Является ли это хорошей реализацией независимого от FPS игрового цикла?

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

изменения:

Для этого вопроса предположим, что msecs() возвращает время, прошедшее в миллисекундах, когда программа запустилась. Реализация msecs отличается на разных платформах. Этот цикл также выполняется по-разному на разных платформах.

#define MSECS_PER_STEP 20
int stepCount, stepSize;  // these are not globals in the real source

void loop() {
    int i,j;
    int iterations =0;
    static int accumulator; // the accumulator holds extra msecs
    static int lastMsec;
    int deltatime = msec() - lastMsec;
    lastMsec = msec();

    // deltatime should be the time since the last call to loop
    if (deltatime != 0) {
        // iterations determines the number of steps which are needed
        iterations = deltatime/MSECS_PER_STEP;

        // save any left over millisecs in the accumulator
        accumulator += deltatime%MSECS_PER_STEP;
    }
    // when the accumulator has gained enough msecs for a step...
    while (accumulator >= MSECS_PER_STEP) {
        iterations++;
        accumulator -= MSECS_PER_STEP;
    }
    handleInput(); // gathers user input from an event queue
    for (j=0; j<iterations; j++) {
        // here step count is a way of taking a more granular step 
        // without effecting the overall speed of the simulation (step size)
        for (i=0; i<stepCount; i++) {
            doStep(stepSize/(float) stepCount); // forwards the sim
        }
    }
}

person Nick Van Brunt    schedule 18.10.2010    source источник
comment
Я знаю, что мне, вероятно, не следует хранить данные о времени в целых числах, но это не совсем то, о чем я спрашиваю здесь.   -  person Nick Van Brunt    schedule 18.10.2010


Ответы (1)


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

#define MSECS_PER_STEP 20
int stepCount, stepSize;  // these are not globals in the real source

void loop() {
    int i,j;
    int iterations =0;

    static int accumulator; // the accumulator holds extra msecs
    static int lastMsec;

Они ничем не инициализированы. Вероятно, они будут равны 0, но вы должны были их инициализировать. Кроме того, вместо того, чтобы объявлять их статическими, вы можете захотеть поместить их в структуру, которую вы передаете в loop по ссылке.

    int deltatime = msec() - lastMsec;

Поскольку lastMsec не было (инициализировано и, вероятно, равно 0), это, вероятно, начинается с большой дельты.

    lastMsec = msec();

Эта строка, как и последняя, ​​вызывает msec. Это, вероятно, подразумевается как «текущее время», и эти вызовы достаточно близки, чтобы возвращаемое значение, вероятно, было одинаковым для обоих вызовов, что, вероятно, также является тем, что вы ожидали, но тем не менее вы вызываете функцию дважды. Вы должны изменить эти строки на int now = msec(); int deltatime = now - lastMsec; lastMsec = now;, чтобы избежать двойного вызова этой функции. Текущие функции получения времени часто имеют гораздо более высокие накладные расходы, чем вы думаете.

    if (deltatime != 0) {
        iterations = deltatime/MSECS_PER_STEP;
        accumulator += deltatime%MSECS_PER_STEP;
    }

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

    while (accumulator >= MSECS_PER_STEP) {
        iterations++;
        accumulator -= MSECS_PER_STEP;
    }

Этот цикл нуждается в комментарии. Его тоже не должно быть. Похоже, его можно было заменить на iterations += accumulator/MSECS_PER_STEP; accumulator %= MSECS_PER_STEP;. Деление и модуль должны выполняться за более короткое и последовательное время, чем цикл на любой машине с аппаратным разделением (что многие делают).

    handleInput(); // gathers user input from an event queue

    for (j=0; j<iterations; j++) {
        for (i=0; i<stepCount; i++) {
            doStep(stepSize/(float) stepCount); // forwards the sim
        }
    }

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

Кроме того, я могу догадаться, что означает цикл j (внешний цикл), но внутренний цикл мне не ясен. также значение, переданное функции doStep — что это значит.

}

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

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

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

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

Другой способ сделать это, который функционально подобен тому, что у вас есть, состоит в том, чтобы последним шагом обработки каждого события таймера была постановка в очередь следующего события таймера (обратите внимание, что никто другой не должен отправлять события таймера {кроме первого). one}, если вы выбрали именно этот способ реализации игры). Это означало бы отказ от регулярных временных интервалов между событиями таймера, а также ограничение способности программы засыпать, поскольку, по крайней мере, каждый раз, когда проверяется очередь событий, будет обрабатываться событие таймера.

person nategoose    schedule 18.10.2010
comment
фантастический ответ - здесь много пищи для размышлений. Добавлю несколько комментариев. - person Nick Van Brunt; 19.10.2010