2011-12-18 2 views
0

Я пытаюсь скопировать логику Sonic physics engine, которая была написана для системы с фиксированным сроком (60 FPS) в переменном возрасте времени (Slick2D, если быть точным).Переменные временные метки и сила тяжести/трение

В оригинале при нажатии кнопки перехода игрок velocity.y настроен на -6,5, а для отметки velocity.y каждый отметит 0,21875.

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

E.g. если мы ожидаем, что кадр займет 16 мс, а он сделает займет 16 мс, цикл будет повторяться один раз и пройдет thisMiniTick как 1. Если дельта не была 16 мс, а 40 мс, цикл будет выполняться три раза, проходя 1, 1, и, наконец, 0,5.

Я ошибочно думал, что в каждой из этих внутренних циклов обновления я мог бы сделать velocity.y += (gravity * thisMiniTickRelative), но это не сработает. При более быстрых кадрах применяется недостаточная гравитация, вызывающая более высокий скачок, а на более медленных кадрах скачок ниже (хотя и не так близко, как заметно).

Есть ли способ сделать это, который будет работать практически для всех кадров, или я должен прибегнуть к установке верхней и нижней границы для delta?

«внутренней обновление» петля:

float timeRemaining = delta/1000f; 
    while(timeRemaining > 0) 
    { 
     float thisMiniTick = Math.min(timeRemaining, 1f/FRAMES_PER_SECOND); 
     float thisMiniTickRelative = thisMiniTick/(1f/FRAMES_PER_SECOND); 

     updateInput(container, game, thisMiniTickRelative); 
     if (playerAirState) 
     { 
      playerVelocity.y += (GRAVITY * thisMiniTickRelative); 
     } 
     clampPlayerVelocity(); 
     playerPosition.add(playerVelocity); 
     doCollisions(); 
     timeRemaining -= thisMiniTick; 
    } 

ответ

2

Не думайте об этом как «прибегая к установке верхней и нижней границы для delta». Ваше приложение и потоки зависят от времени планирования ОС для вашего приложения, среди всех других требований к системе, и того, что вам просто нужно знать. Эта задача столь же старая в компьютерных играх, как и в тот день, когда мы перешли от однозадачных операционных систем к многозадачным операционным системам.

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

В проектах, над которыми я работал, в том числе в Slick, я нахожу, что все, что было в 30-60 логических обновлениях в секунду (от 30,3 миллисекунд до 16,6 миллисекунд между обновлениями), отлично работает и дает вам необходимую гладкость от вашего движения, физики и расчетов столкновений.

Буквально то, что средства есть, в течение 30-60 логических обновлений в секунду диапазона, что вы хотите сделать следующее:

container.setMinimumLogicUpdateInterval(16); // max 60 logic updates per second 
container.setMaximumLogicUpdateInterval(31); // min 30 logic updates per second 

Кроме того, это общая ошибка, чтобы попытаться вычислить значение timeRemaing, но вы не хотите этого делать. Вы просто хотите умножить, сколько вы двигаетесь, сколько времени прошло. Если прошло 30 миллисекунд, это примерно 1/33 секунды, поэтому вы должны перенести свой игровой объект на 1/33 часть суммы, которую он будет перемещать за 1 секунду.

float timeElapsed = delta/1000f; 

playerVelocity.y += (GRAVITY * timeElapsed); 

С верхних/нижних пределов, установленных, как указано выше, вы будете уверены, что timeElapsed всегда будет иметь значение между 0.03 и 0.06. Если ваша игра увязнет и ваша частота кадров замедляется, ваши логические обновления по-прежнему не выйдут за пределы этих границ.Вместо этого произойдет то, что вся игра будет замедляться (как и в старые времена Sega, когда на экране было слишком много), но расчеты на столкновения и физика будут по-прежнему работать, как ожидалось.

+0

Спасибо за подробный ответ, это обнадеживает, чтобы увидеть, что нет никакого волшебного решения, которое я не мог понять. Почему вы считаете ошибку timeRemaining ошибкой? У меня первоначально была скорость, умноженная на время, прошедшее, но это означало, что коллизионный код нуждался в большой перезаписи, поскольку игрок мог проехать больше, чем целая плитка за один тик и, таким образом, «пропустить» плитки. –

+0

Да, я, возможно, переоценил это, но я предполагаю, что, когда я видел, что 'timeRemaining' идет наперекосяк, это когда люди пытаются использовать это значение, чтобы выяснить, сколько еще времени им нужно делать другими вещами, такими как вычисления AI и т. Д. Это не значит, что вы не можете его использовать, просто с помощью различных способов решения этой проблемы, привязка всего к значению 'delta' даст вам самые надежные результаты. – jefflunt

+0

Что касается игроков, движущихся слишком быстро для столкновения, есть несколько способов подойти к этой проблеме. Можно было бы сделать расчет столкновения по всему пространству, пройденному игроком (если игрок переместил 3 плитки, то вам нужно выяснить, что произошло с каким-либо из них, и отскочить назад. Другой, гораздо более простой путь - либо увеличить детализацию ваших логических обновлений (корректируя интервалы обновления макс/макс логики), либо заставить игрока двигаться медленнее. – jefflunt

Смежные вопросы