Как создать таймер для игры в квест

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

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

Чего я хочу

Я решил, что хочу установить трехминутный таймер для своей игры. Когда вы нажимаете «Начать игру», мне нужно, чтобы игровое поле отображалось вместе с таймером, который говорит Time Remaining: 3:00. Я хочу, чтобы это начало обратный отсчет прямо сейчас. После запуска я не хочу, чтобы он останавливался, пока не появится 0:00. Как только это произойдет, я хочу, чтобы вместо этого компонент отображал Times Up!.

Что мне нужно

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

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

Очевидно, что это ничего не даст, кроме отображения статического оператора Time Remaining: 3:00 на странице, но это хорошее место для начала. Я знаю, что для того, чтобы постоянно обновлять время, мне нужно установить его состояние. Я не могу сохранить 3:00 в состоянии и рассчитывать, что смогу вычесть из него единицу, чтобы получить 2:59. Есть несколько причин, по которым это не сработает, но самая простая из них заключается в том, что 3:00 не является числом. Из-за этого его нужно было бы сохранить в состоянии как строку, и поэтому я не смог бы уменьшить его таким образом.

Это означает, что вместо одного ключа time мне понадобится один ключ для minutes и один для seconds.

state = {
  minutes: 3,
  seconds: 0
}

Пока достаточно просто, хотя теперь я должен использовать это недавно созданное состояние в моем рендере.

render() {
  const { minutes, seconds } = this.state
  return (
    <div>
      <h1>Time Remaining: { minutes }:{ seconds }</h1>
    </div>
  )
}

После рендеринга вышеупомянутого я заметил первое небольшое препятствие. На начальном рендере мы увидим Time Remaining: 3:0. Не идеально. Я решил справиться с этим с помощью простого троичного выражения в своем операторе возврата.

{ minutes }:{ seconds < 10 ? `0${ seconds }` : seconds }

Это гарантирует, что секунды всегда будут отображаться как две цифры. Идеально! А теперь давайте начнем тикать эти часы.

ТИК Так

Я хочу, чтобы таймер начинал обратный отсчет, как только компонент монтируется, и я знаю, что я хочу, чтобы таймер уменьшался один раз в секунду. Это означает, что мне нужно использовать setInterval внутри componentDidMount. setInterval будет отвечать за то, чтобы состояние моего seconds было на единицу меньше, чем было на секунду раньше. Это должно выглядеть примерно так:

componentDidMount() {
  this.myInterval = setInterval(() => {
    this.setState(({ seconds }) => ({
      seconds: seconds - 1
    }))
  }, 1000)
}

Хорошо, это начало. Но поскольку мы запустили наш таймер с нуля секунд, мы сразу переходим к отрицательным числам, а это не то, что мы здесь искали. Внутри setInterval мне нужно настроить оператор if, который будет проверять, установлено ли состояние на отрицательное число. Если это так, мне нужно установить состояние моего seconds на 59 вместо этого отрицательного числа. Помимо того, что я вернул секунды обратно на 59, я должен воспользоваться этой возможностью, чтобы уменьшить мои минуты.

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

componentDidMount() {
  this.myInterval = setInterval(() => {
    const { seconds, minutes } = this.state
    if (seconds > 0) {
      this.setState(({ seconds }) => ({
        seconds: seconds - 1
      }))
    }
    if (seconds === 0) {
      if (minutes === 0) {
        clearInterval(this.myInterval)
      } else {
        this.setState(({ minutes }) => ({
          minutes: minutes - 1,
          seconds: 59
        }))
      }
    }
  }, 1000)
}

Идеально! Обратный отсчет начинается, как только компонент монтируется, и заканчивается, когда достигает 0:00. Это почти именно то, что мы искали. Мне нужно решить только одну вещь - убрать таймер с экрана, когда он достигнет 0:00, и вместо этого отобразить Times Up!.

Для этого нам просто понадобится еще один простой тройной элемент в моем операторе return, который будет проверять, установлены ли минуты и секунды на ноль.

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

После всего этого вот код, который у меня получился:

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