Memperbaiki timestep & rendering game loop
Jadi saya menulis putaran waktu & rendering game tetap ini. Saya mempostingnya di sini untuk melihat apakah ada cara untuk mengoptimalkannya / membuatnya lebih akurat.
const float updatesRate = 1 / 60.0f;
const float framesRate = 1 / 308.0f;
double currentTime = hireTimeInSeconds( );
double accumulator = 0.0;
double accumulator2 = 0.0;
uint32_t frames = 0, updates = 0;
double timer = hireTimeInSeconds( );
while ( true ) {
double newTime = hireTimeInSeconds( );
if ( newTime - timer >= 1 ) {
//Printing fps and ups here
updates = 0;
frames = 0;
timer = newTime;
}
double frameTime = newTime - currentTime;
if ( frameTime > 0.25 ) frameTime = 0.25;
currentTime = newTime;
accumulator += frameTime;
accumulator2 += frameTime;
//Polling input here
while ( accumulator >= updatesRate ) {
//Updating here
accumulator -= updatesRate;
updates++;
}
const double alpha = accumulator / updatesRate;
while ( accumulator2 >= framesRate ) {
//Rendering
frames++;
accumulator2 -= framesRate;
}
}
```
Jawaban
const float updatesRate = 1 / 60.0f;
const float framesRate = 1 / 308.0f;
Nilai waktu lainnya adalah semua doubles, agak aneh menggunakan floats di sini.
if ( newTime - timer >= 1 ) {
...
if ( frameTime > 0.25 ) frameTime = 0.25;
Angka ajaib ini harus diberi nama konstanta.
C ++ sekarang menyediakan berbagai utilitas terkait waktu melalui <chrono>header. Kita harus menggunakan jenis jam, jenis durasi aman jenis, dan konversi yang disediakannya.
double accumulator = 0.0;
double accumulator2 = 0.0;
uint32_t frames = 0, updates = 0;
double timer = hireTimeInSeconds( );
Penamaan variabel ini membuat tujuannya sangat tidak jelas. Mungkin kita bisa mendefinisikan Accumulatorkelas untuk mengelompokkan variabel untuk setiap akumulator dan menghapus duplikasinya (lihat contoh kode di bawah).
hireTimeInSecondsIni mungkin seharusnya highResTimeInSeconds.
while ( accumulator2 >= framesRate ) {
//Rendering
frames++;
accumulator2 -= framesRate;
}
Saya rasa ini tidak masuk akal. Kami hanya ingin merender satu bingkai, meskipun banyak waktu telah berlalu. (Kami tidak melakukan pembaruan apa pun antara rendering bingkai dalam loop ini, jadi kami hanya akan merender hal yang persis sama berulang kali).
Menerapkan saran di atas, kita mungkin mendapatkan sesuatu seperti kode di bawah ini. Ini adalah beberapa baris kode lagi, tetapi juga jauh lebih dapat digunakan kembali (terutama kelas accumulatordan frame_timer), dan mudah-mudahan maksudnya lebih jelas:
#include <chrono>
#include <iostream>
template<class c_t>
class accumulator
{
public:
using clock_t = c_t;
using duration_t = typename clock_t::duration;
template<class d_t>
explicit accumulator(d_t tick_length):
m_tick_length(std::chrono::duration_cast<duration_t>(tick_length)) { }
std::size_t accumulate(duration_t delta_time) // note: returns the number of ticks of m_tick_length triggered by this call
{
m_accumulated_time += delta_time;
auto ticks = std::size_t{ 0 };
while (m_accumulated_time >= m_tick_length)
{
++ticks;
m_accumulated_time -= m_tick_length;
}
return ticks;
}
duration_t get_tick_length() const
{
return m_tick_length;
}
private:
duration_t m_tick_length;
duration_t m_accumulated_time;
};
template<class c_t>
class frame_timer
{
public:
using clock_t = c_t;
using duration_t = typename clock_t::duration;
using time_point_t = typename clock_t::time_point;
template<class d_t>
explicit frame_timer(d_t initial_last_frame_time):
m_last_frame_time(std::chrono::duration_cast<duration_t>(initial_last_frame_time)),
m_last_tick(clock_t::now()) { }
void tick()
{
auto now = clock_t::now();
m_last_frame_time = now - m_last_tick;
m_last_tick = now;
}
duration_t get_last_frame_time() const
{
return m_last_frame_time;
}
private:
duration_t m_last_frame_time;
time_point_t m_last_tick;
};
int main()
{
using clock_t = std::chrono::high_resolution_clock;
using duration_t = clock_t::duration;
using time_point_t = clock_t::time_point;
using seconds_t = std::chrono::duration<float>;
auto const update_time = seconds_t(1.f / 60.f);
auto update_accumulator = accumulator<clock_t>(update_time);
auto const render_time = seconds_t(1.f / 308.f);
auto render_accumulator = accumulator<clock_t>(render_time);
auto const print_time = seconds_t(1.f);
auto print_accumulator = accumulator<clock_t>(print_time);
auto const initial_frame_time = update_time;
auto timer = frame_timer<clock_t>(initial_frame_time);
auto const max_frame_time = std::chrono::duration_cast<duration_t>(seconds_t(0.25f));
while (true)
{
auto const last_frame_time = (timer.get_last_frame_time() > max_frame_time) ? max_frame_time : timer.get_last_frame_time();
if (print_accumulator.accumulate(last_frame_time) != 0) // note: will skip update periods if frames are super long (> print_time)
{
// ... print frames
std::cout << "ping!" << std::endl;
}
auto const update_ticks = update_accumulator.accumulate(last_frame_time); // note: don't put this directly in the loop condition, we only want to call it once!
for (auto i = std::size_t{ 0 }; i != update_ticks; ++i)
{
// ... do update
std::cout << "update" << std::endl;
}
if (render_accumulator.accumulate(last_frame_time) != 0) // note: only ever render 1 frame, even if we should have rendered more
{
// ... render
std::cout << "render" << std::endl;
}
timer.tick();
}
}