Как заставить что-то происходить каждые N секунд в игре?

8

Я хочу, чтобы что-то происходило каждые N секунд, как бы я это сделал? Может быть, использовать таймер, но как?

user2957632
источник
Я сделал ваш вопрос более общим, так как думаю, что таким образом он будет полезен большему количеству людей.
MichaelHouse
Основываясь на комментариях к одному из ответов, я не верю, что автор ищет бит таймера, но как изменить рамку спрайта. Это больше зависит от того, о какой платформе мы говорим. Несмотря на это, теперь, когда вопрос был отредактирован, он не отражает того, что он действительно задает. Java 2D была важна для вопроса, который он пытается задать.
прототип
Изменение спрайта и выполнение этого каждые 5 секунд не связаны и должны задаваться как разные вопросы. Пожалуйста, задайте новый вопрос об изменении спрайта. Убедитесь, что вы включили то, что вы пробовали, а что с ним не работает.
MichaelHouse

Ответы:

14

Обычный подход для этого заключается в использовании цикла обновления и дельта-времени.

float timerCurrent = 0f;
float timerTotal = 5f;


Update() {
    timerCurrent += deltaTime;
    if(timerCurrent >= timerTotal) {
        //do timer action
        timerCurrent -= timerTotal;
    }
}

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

MichaelHouse
источник
Является ли это в каком-либо техническом смысле более предпочтительным, чем запуск явного таймера, как в ответе @ Josh?
Джек М
@JackM Это будет зависеть от реализации таймера. Причина, по которой я ответил так, заключается в том, что она не зависит от языка и очень проста в реализации.
MichaelHouse
1
@JackM: Это также предпочтительнее, если вы когда-либо работали под отладчиком. Накопленное игровое время может остановиться, когда вы достигнете точки останова, вместо того, чтобы истечь каждый таймер и немедленно начать огонь.
Бен Джексон
Нужно быть осторожным, так как игра может зависнуть, например, и тики могут быть потеряны. Поэтому нужно вызывать действие таймера для каждого тика с указанной пройденной дельтой, а не только один раз и т. Д.
Кристиан Ивицевич
Этот метод не основан на подсчете тиков, он основан на дельта-времени. Если есть очень длинный кадр, то время дельты тоже очень большое. Однако во многих случаях предпочтительнее ограничить дельта-время, поскольку слишком длинные кадры могут вызвать проблемы с физикой и тому подобное. Тогда этот метод лучше использовать просто текущее время минус время начала, поскольку он лучше синхронизируется с другими вычислениями в игре при ограничении диапазона времени дельты.
MichaelHouse
3

В большинстве языков вам нужно запустить часы с помощью какого-либо вызова функции по аналогии с clock.startTimer()тем, как вы входите в основной цикл. Затем вы должны сохранить значение времени часов перед тем, как ввести основной цикл в переменную с такой функцией time = clock.getTime(). Сохраните время вашего шага в переменной n.

В вашем основном цикле проверка, которую вы хотите сделать, является чем-то вроде

if(time + n <= clock.getTime())
     //do stuff
     time = clock.getTime() //important to assign new time value here

Примечание: я не уверен, на каком языке / библиотеке вы смотрите, так что это всего лишь общий алгоритм, который я придумал не покладая рук. Это может не совсем соответствовать вашим потребностям. Некоторые языки / библиотеки требуют, чтобы вы создали объект часов, в то время как другие вы можете просто сделать вызов функции напрямую.

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

мистифицировать
источник
1
Хороший ответ. Это может вызвать проблемы с многопоточностью на некоторых языках (например, .NET), когда таймер реализован в отдельном потоке от игрового цикла.
ashes999
1
Также важно отметить, что если timeбудет храниться прошедшее игровое время, он должен использовать формат с плавающей запятой двойной точности.
Кевинтодиско
Это хорошие моменты, я добавлю их в ответ.
Джош
2

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

float elapsed = 0.0f;
float duration = 4.0f;

void update(float deltaTime) {
    elapsed += deltaTime;
    if (elapsed <= duration) {
        // Run code.
        elapsed = 0.0f;
        // Maybe remove from update loop?
    }
}

То, как вы хотите справиться с // Run code.частью, зависит от вас. Возможно, у вас есть Timerкласс, экземпляр которого принимает delegate(C #) или callback(C ++) и вызывает его по истечении таймера. Кроме того, приостановка таймера является вопросом удаления его из цикла обновления.

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

double startTime = 0.0; // This is a double for a reason, I'll explain below.
bool running = false;

void start() {
    startTime = getTime(); // This is whatever system time call you want to use.
    running = true;
}

double getElapsed() {
    double elapsed = getTime() - startTime;
    return elapsed;
}

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

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

Вы заметите, что во втором примере я использовал doubleтип вместо a float, как и в первом примере (имя типа, конечно, зависит от языка, который вы используете). Причиной этого является второй пример - хранение общего прошедшего игрового времени . Если игра остается запущенной в течение очень долгого времени, значения формата с плавающей запятой одинарной точности будут иметь недостаточную точность для точного измерения истекшего времени , что приведет к проблемам со стабильностью. Первый пример подходит для использования floatтипа, если не ожидается большой продолжительности.

На другом конце спектра, если ожидаемое время обновления достаточно мало, вы, возможно, не сможете достичь его изначально, в зависимости от того, как вы получаете системное время. Таймеры часто зависят от разрешения таймера ОС, на которой вы работаете. Например, разрешение таймера по умолчанию в Windows 7 и ниже составляет 15,6 мс . Это означает, что наименьшая точность таймера, которую вы могли бы достичь, используя такие функции, как timeGetTimeили GetTickCount, составляет 15,6 мс, если вы не измените разрешение таймера ( с этим тоже связаны опасности ).

Что ж, это оказалось немного долго, но это важные вещи, о которых следует помнить при реализации таймеров.

kevintodisco
источник
спасибо, но теперь я знаю, как это сделать, но как мне изменить спрайт назад и
вперёд
@ user2957632 Это не то, что ваш вопрос сейчас задает. Пожалуйста, сделайте это более понятным.
Кевинтодиско