Hareketin düzgün görünmesi için iki şey çok önemlidir, birincisi, oluşturduğunuz şeyin çerçevenin kullanıcıya sunulduğu sırada beklenen durumla eşleşmesi gerektiğidir, ikincisi kullanıcıya çerçeveler sunmanız gerekir göreceli olarak sabit bir aralıkta. T + 10ms'de bir çerçeve, sonra T + 30ms'de bir çerçeve, sonra T + 40ms'de bir çerçeve sunmak, o zamanlar için gerçekte gösterilen şey simülasyona göre doğru olsa bile kullanıcıya titriyor gibi görünecektir.
Ana döngünüzde yalnızca düzenli aralıklarla render ettiğinizden emin olmak için herhangi bir geçit mekanizması bulunmuyor gibi görünüyor. Bazen renderler arasında 3 güncelleme yapabilir, bazen 4 yapabilirsiniz. Temel olarak, döngünüz mevcut zamanın önüne itmek için yeterli zamanı simüle ettikten sonra mümkün olduğunca sık işleyecektir. sonra bu durumu oluşturun. Ancak güncelleme veya oluşturma süresinde ve çerçeveler arasındaki aralıkta da değişiklik olabilir. Simülasyonunuz için sabit bir zaman adımınız var, ancak oluşturma işleminiz için değişken bir zaman adımınız var.
Muhtemelen ihtiyacınız olan şey, render işleminizden hemen önce bir beklemedir; İdeal olarak bu uyarlanabilir olmalıdır: güncellemek / işlemek için çok uzun sürdüyseniz ve aralığın başlangıcı zaten geçtiyse, derhal işlemelisiniz, ancak sürekli olarak render edip güncelleyip hala devam edene kadar aralık uzunluğunu artırmalısınız. aralık bitmeden bir sonraki oluşturma. Yedeklemek için bol zamanınız varsa, daha hızlı işlemek için aralığı yavaşça azaltabilir (yani kare hızını artırabilirsiniz).
Ancak, işte bakıcı, simülasyon durumunun "şimdi" olarak güncellendiğini tespit ettikten hemen sonra çerçeveyi oluşturmazsanız, geçici takma adı uygularsınız. Kullanıcıya sunulan çerçeve biraz yanlış zamanda sunulur ve kendi içinde bir kekik gibi hissedilir.
Okuduğunuz makalelerde gördüğünüz "kısmi zaman adımının" nedeni budur. İyi bir nedenden dolayı oradadır ve bunun nedeni, fizik zaman testinizi sabit oluşturma zaman testinizin bazı sabit integral katlarına sabitlemezseniz, çerçeveleri doğru zamanda sunamazsınız. Sonuçta onları çok erken veya çok geç sunuyorsunuz. Sabit bir oluşturma oranı elde etmenin ve hala fiziksel olarak doğru olan bir şeyi sunmanın tek yolu, oluşturma aralığı ortaya çıktığında, büyük olasılıkla sabit fizik zamanlarınızın ikisinin ortasında olacağınızı kabul etmektir. Ancak bu, oluşturma sırasında nesnelerin değiştirildiği anlamına gelmez, yalnızca oluşturma işleminin, nesnelerin nerede olduklarını geçici olarak belirlemeleri gerekir; böylece, onları daha önce bulundukları yer ile güncellemeden sonraki konum arasında bir yerde oluşturabilir. Bu önemlidir - render için dünya durumunu asla değiştirmeyin, sadece güncellemeler dünya durumunu değiştirmelidir.
Yani bir sözde kod döngüsüne koymak için, daha fazla bir şeye ihtiyacınız olduğunu düşünüyorum:
InitialiseWorldState();
previousTime = currentTime = 0.0;
renderInterval = 1.0 / 60.0; //A nice high starting interval
subFrameProportion = 1.0; //100% currentFrame, 0% previousFrame
while (true)
{
frameStart = ActualTime();
//Render the world state as if it was some proportion
// between previousTime and currentTime
// E.g. if subFrameProportion is 0.5, previousTime is 0.1 and
// currentTime is 0.2, then we actually want to render the state
// as it would be at time 0.15. We'd do that by interpolating
// between movingObject.previousPosition and movingObject.currentPosition
// with a lerp parameter of 0.5
Render(subFrameProportion);
//Check we've not taken too long and missed our render interval
frameTime = ActualTime() - frameStart;
if (frameTime > renderInterval)
{
renderInterval = frameTime * 1.2f; //Give us a more reasonable render interval that we actually have a chance of hitting
}
expectedFrameEnd = frameStart + renderInterval;
//Loop until it's time to render the next frame
while (ActualTime() < expectedFrameEnd)
{
//step the simulation forward until it has moved just beyond the frame end
if (previousTime < expectedFrameEnd) &&
currentTime >= expectedFrameEnd)
{
previousTime = currentTime;
Update();
currentTime += fixedTimeStep;
//After the update, all objects will be in the position they should be for
// currentTime, **but** they also need to remember where they were before,
// so that the rendering can draw them somewhere between previousTime and
// currentTime
//Check again we've not taken too long and missed our render interval
frameTime = ActualTime() - frameStart;
if (frameTime > renderInterval)
{
renderInterval = frameTime * 1.2f; //Give us a more reasonable render interval that we actually have a chance of hitting
expectedFrameEnd = frameStart + renderInterval
}
}
else
{
//We've brought the simulation to just after the next time
// we expect to render, so we just want to wait.
// Ideally sleep or spin in a tight loop while waiting.
timeTillFrameEnd = expectedFrameEnd - ActualTime();
sleep(timeTillFrameEnd);
}
}
//How far between update timesteps (i.e. previousTime and currentTime)
// will we be at the end of the frame when we start the next render?
subFrameProportion = (expectedFrameEnd - previousTime) / (currentTime - previousTime);
}
Bunun çalışabilmesi için güncellenen tüm nesnelerin nerede olduklarını ve şimdi nerede olduklarını bilmeleri gerekir, böylece render nesnenin nerede olduğu bilgisini kullanabilir.
class MovingObject
{
Vector velocity;
Vector previousPosition;
Vector currentPosition;
Initialise(startPosition, startVelocity)
{
currentPosition = startPosition; // position at time 0
velocity = startVelocity;
//ignore previousPosition because we should never render before time 0
}
Update()
{
previousPosition = currentPosition;
currentPosition += velocity * fixedTimeStep;
}
Render(subFrameProportion)
{
Vector actualPosition =
Lerp(previousPosition, currentPosition, subFrameProportion);
RenderAt(actualPosition);
}
}
Ve oluşturma işleminin tamamlanması 3 ms sürüyor, güncelleme 1 ms sürüyor, güncelleme zaman adımınız 5 ms olarak sabitleniyor ve oluşturma zaman aşamanız 16 ms [60Hz] 'de başlıyor (ve kalıyor) diyerek milisaniye cinsinden bir zaman çizelgesi oluşturalım.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
R0 U5 U10 U15 U20 W16 R16 U25 U30 U35 W32 R32
- İlk önce 0 zamanında başlatırız (yani currentTime = 0)
- Dünyayı 0 zamanında çekecek olan 1.0 (% 100 currentTime) oranı ile render yapıyoruz
- Bu bittiğinde, gerçek zaman 3'tür ve çerçevenin 16'ya kadar bitmesini beklemiyoruz, bu yüzden bazı güncellemeler yapmamız gerekiyor
- T + 3: 0'dan 5'e güncelleme yapıyoruz (bu nedenle daha sonra currentTime = 5, previousTime = 0)
- T + 4: çerçeve sonundan önce, bu yüzden 5'ten 10'a güncelliyoruz
- T + 5: çerçeve sonundan önce, bu yüzden 10'dan 15'e güncelleme yapıyoruz
- T + 6: çerçeve sonundan önce, bu yüzden 15'ten 20'ye güncelleme yapıyoruz
- T + 7: hala çerçeve sonundan önce, ancak currentTime kare sonunun hemen ötesinde. Daha fazla simüle etmek istemiyoruz çünkü bunu yapmak, bizi bir sonraki render etmek istediğimiz zamanın ötesine itecektir. Bunun yerine bir sonraki oluşturma aralığı için sessizce bekleriz (16)
- T + 16: Tekrar oluşturma zamanı. previousTime 15, currentTime 20'dir. Dolayısıyla, T + 16'da işlemek istiyorsak, 5 ms uzunluğunda 1 ms'lik yoldayız. Yani çerçevenin% 20'si kadar yoldayız (oran = 0.2). Oluşturduğumuzda, nesneleri önceki konumları ile mevcut konumları arasında% 20 oranında çizeriz.
- 3'e geri dönün ve süresiz olarak devam edin.
Burada çok önceden simüle etme konusunda başka bir nüans daha var, yani çerçeve gerçekten oluşturulmadan önce olmasına rağmen kullanıcının girdileri yok sayılabilir, ancak döngünün sorunsuz bir şekilde taklit edildiğinden emin olana kadar endişelenmeyin.