Рамочное независимое движение

11

Я читал две другие темы здесь о движении: движение на основе времени против движения на основе частоты кадров? , и когда я должен использовать фиксированный или переменный шаг по времени?

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

Я следую инструкциям SDL от lazyfoo и наткнулся на независимый от фрейма урок. http://lazyfoo.net/SDL_tutorials/lesson32/index.php

Я не уверен, что пытается сказать движущаяся часть кода, но я думаю, что это так (пожалуйста, исправьте меня, если я ошибаюсь): чтобы иметь независимое от фрейма движение, нам нужно выяснить, как далеко находится объект ( например, спрайт) перемещается в течение определенного периода времени, например 1 секунда. Если точка движется со скоростью 200 пикселей в секунду, то мне нужно вычислить, сколько она движется в течение этой секунды, умножив 200 pps на 1/1000 секунды.

Это правильно? Урок говорит:

«скорость в пикселях в секунду * время с последнего кадра в секундах. Поэтому, если программа работает со скоростью 200 кадров в секунду: 200 pps * 1/200 секунд = 1 пиксель»

Но ... я думал, что мы умножаем 200 pps на 1/1000 секунды. Что это за бизнес с кадрами в секунду?

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

Спасибо.

Сложение:

SDL_Rect posRect;
posRect.x = 0;
posRect.y = 0;

float y, yVel;
y = 0;
yVel = 0;

Uint32 startTicks = SDL_GetTicks();

bool quit = false;
SDL_Event gEvent;

while ( quit == false )
{
    while ( SDL_PollEvent( &gEvent ) )
    {
        if ( gEvent.type == SDL_QUIT )
            quit = true;
    }

    if ( y <= 580 )
    {
        yVel += DOT_VEL;
        y += (yVel * (SDL_GetTicks() - startTicks)/1000.f);
        posRect.y = (int)y;
    }

    startTicks = SDL_GetTicks();
    SDL_BlitSurface( bg, NULL, screen, NULL );
    SDL_BlitSurface( dot, NULL, screen, &posRect );
    SDL_Flip( screen );
}

Это код, который перемещает точку вниз по экрану. Я думаю, что я все правильно до сих пор. Она движется вниз по экрану, но происходит нечто странное, что я не могу объяснить. Предполагается, что точка останется на уровне y = 580, когда достигнет значения, превышающего это значение y. Однако каждый раз, когда я запускаю программу, точка оказывается в другом месте, что означает чуть больше 580, поэтому точка находится на полпути или больше, чем на полпути от экрана (точка составляет 20 пикселей, экран размеры 800х600). Если я что-то сделаю, например, нажмите и удерживайте строку заголовка программы, а затем отпустите, точка исчезнет с экрана. Почему это каждый раз переменное? Что касается проблемы с заголовком, я думаю, это потому, что, когда я держу заголовок, таймер все еще работает, а истекшее время становится больше, в результате чего большее расстояние точечные движется в следующем кадре. Это правильно?

ShrimpCrackers
источник
Ваше дополнение на самом деле другой вопрос. Вы должны задать второй вопрос вместо того, чтобы добавить его к уже существующему. На это легко ответить, хотя: просто вычислите y-движение, например. yMovement = (yVel * (SDL_GetTicks() - startTicks)/1000.f);затем сделайте:if(y + yMovement <= 580){ y += yMovement; } else { y = 580; }
Буммзак

Ответы:

10

ПРИМЕЧАНИЕ. Все дроби должны быть плавающими.

Рамочное независимое движение работает, основывая движение вне времени. Вы получаете количество времени, потраченное с момента последнего кадра (поэтому, если в одной секунде 60 кадров, каждый кадр занимает 1,0 / 60,0 секунд, если все кадры занимают одинаковое количество времени), и узнаете, сколько движения это переводится в.

Если вы хотите, чтобы ваша сущность перемещала определенное пространство на определенную единицу времени (скажем, 100 пикселей за каждую секунду времени), вы можете узнать, сколько пикселей вы должны переместить за кадр, умножив количество движения на секунда (100 пикселей) на количество времени, прошедшего в секундах (1,0 / 60,0), чтобы выяснить, сколько движения должно происходить в текущем кадре.

Он работает, вычисляя, сколько движений вы должны выполнить за кадр, используя количество прошедшего времени и скорость, определенную в какой-то единице времени (предпочтительнее секунды или миллисекунды). Таким образом, ваш расчет может выглядеть так:movementPerSecond * (timeElapsedInMilliseconds / 1000.0)

Надеюсь, это имело для вас какой-то смысл.

Я хочу переместить моего парня на 200 пикселей вправо каждую секунду. Если текущий кадр запускается через 50 миллисекунд после предыдущего кадра, то я должен переместить моего парня на долю скорости, которая была ранее указана (которая составляла 200 пикселей). Я должен переместить его на 50/1000 расстояния, потому что прошло только 1/20 (50/1000 = 1/20) времени. Поэтому было бы разумно переместить его только на 10 пикселей (если бы произошло еще 19 кадров, расположенных на расстоянии 50 миллисекунд друг от друга, то общее количество движения в эту секунду составило бы 200 пикселей, то есть желаемое количество).

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

Кадров в секунду: это количество кадров в секунду. Обычно частота кадров - это то, сколько раз игра рисуется / воспроизводится в секунду, или сколько раз игровой цикл завершается за секунду.

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

Майкл Коулман
источник
В приведенном вами примере 50 миллисекунд - это время для каждого кадра, верно? И что было рассчитано на 1000 / FPS? И поэтому движение, которое нужно сделать для каждого кадра, составляет пиксели в секунду * 50/1000?
Креветки
хм, хотя я понял, что миллисекунды для каждого таймфрейма, вероятно, будут переменными, не так ли? Что-то вроде getTicks () - startTicks всегда будет другим и не постоянным.
Креветочные крекеры
@Omnion: если вы укажете расстояние в «пикселях в секунду», вы не сможете использовать миллисекунды ... оно должно быть 1,0 / 60,0, а не 1000/60, что приведет к чему-то совершенно другому.
Буммзак
@ShrimpCrackers да, прошедшее время меняется. Представьте себе старый компьютер, который не способен воспроизводить 60 кадров в секунду. Вы по-прежнему хотите, чтобы игра на такой машине работала с той же скоростью (но не с одинаковым fps).
Bummzack
Итак, что же в учебнике lazyfoo, что означает 1000 в deltaticks / 1000.f? FPS? 1000 миллисекунд? Я немного смущен прямо сейчас. Кажется, что FPS необходим для определения времени, необходимого для каждого кадра, но на самом деле он не учитывает движение.
Креветочные крекеры
7

В динамике фреймов ваш код для (например) перемещения объекта будет выглядеть следующим образом:

x = x + speedPerFrame

Если вы хотите быть независимыми от фреймов, это может выглядеть так:

x = x + speedPerSecond * secondsElapsedSinceLastFrame
Воутер Ливенс
источник
спасибо, это имеет смысл. У меня есть еще один вопрос выше.
Креветки
1

По поводу дополнительного вопроса.

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

На последнем кадре перед пересечением 580 вы могли бы начинать с 579, или вы могли бы быть на 570 или на 100. Вы также можете шагать на 1 пиксель вперед или на 1000, в зависимости от того, сколько времени потребовалось для выполнения последнего кадра.

Просто измените ваше условие IF на что-то вроде этого, и все будет в порядке.

if ( y <= 580 )
{
    yVel += DOT_VEL;
    y += (yVel * (SDL_GetTicks() - startTicks)/1000.f);
    if( y > 580 )
    {
        y = 580;
    }
    posRect.y = (int)y;
}
Тим О'Нил
источник