İşte fizik simülasyon döngünüzü geliştirmek için gerekli adımlar.
1. Zaman Aşımı
Kodunuzla görebildiğim temel sorun, fizik adım süresini hesaba katmamasıdır. Position += Velocity;
Birimlerin eşleşmediği için yanlış bir şey olduğu açık olmalıdır . Ya Velocity
aslında bir hız değil ya da bir şey eksik.
Senin hız ve yerçekimi değerleri her kare bir zaman biriminde gerçekleşir şekilde ölçekli bile 1
(yani mesela. Velocity
Aslında demektir mesafe , bir saniyede gitti) zaman yere görünmelidir ya kodunuzda örtük böylece değişkenleri düzelterek ( isimleri gerçekten depoladıkları şeyi yansıtır) veya açıkça (bir zaman çizelgesi getirerek). Yapılacak en kolay şeyin zaman birimini beyan etmek olduğuna inanıyorum:
float TimeStep = 1.0;
Ve bu değeri ihtiyaç duyulan her yerde kullanın:
Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...
İyi bir derleyicinin çarpımları basitleştireceğine dikkat edin 1.0
, böylece bu parça işleri yavaşlatmaz.
Şimdi Position += Velocity * TimeStep
hala tam olarak doğru değil ( nedenini anlamak için bu soruya bakın ) ama muhtemelen şimdilik yapacak.
Ayrıca, bunun dikkate alınması gerekir:
Velocity *= Physics.Air.Resistance;
Düzeltmek biraz daha zor; olası bir yol:
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
Math.Pow(Physics.Air.Resistance.Y, TimeStep))
* Velocity;
2. Çift güncelleme
Şimdi zıplarken ne yaptığınızı kontrol edin (yalnızca ilgili kod gösterilir):
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Position.Y + Velocity.Y * TimeStep;
}
TimeStep
Sıçrama sırasında bunun iki kez kullanıldığını görebilirsiniz . Bu temelde topa kendini güncellemesi için iki kat daha fazla zaman veriyor. Bunun yerine ne olması gerekiyor:
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
/* First, stop at Y = 0 and count how much time is left */
float RemainingTime = -Position.Y / Velocity.Y;
Position.Y = 0;
/* Then, start from Y = 0 and only use how much time was left */
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Velocity.Y * RemainingTime;
}
3. Yerçekimi
Kodun bu bölümünü şimdi kontrol edin:
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
Çerçevenin tüm süresi boyunca yerçekimi eklersiniz. Peki ya top gerçekten bu çerçevede sekerse? Daha sonra hız ters çevrilecek, ancak eklenen yerçekimi topun yerden uzaklaşmasını sağlayacaktır! Bu nedenle, zıplarken aşırı yer çekiminin kaldırılması ve ardından doğru yönde yeniden eklenmesi gerekecektir .
Yerçekiminin doğru yönde yeniden eklenmesi bile hızın çok fazla hızlanmasına neden olabilir. Bundan kaçınmak için yerçekimi ilavesini atlayabilirsiniz (sonuçta o kadar da değil ve sadece bir çerçeve sürer) veya hızı sıfıra kelepçeleyebilirsiniz.
4. Sabit kod
İşte tam güncellenmiş kod:
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep);
}
public void Update(float TimeStep)
{
float RemainingTime;
// Apply gravity if we're not already on the ground
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, RemainingTime),
Math.Pow(Physics.Air.Resistance.Y, RemainingTime))
* Velocity;
Position += Velocity * TimeStep;
if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
{
// We've hit a vertical (side) boundary
if (Position.X < 0)
{
RemainingTime = -Position.X / Velocity.X;
Position.X = 0;
}
else
{
RemainingTime = (Position.X - (GraphicsViewport.Width - Texture.Width)) / Velocity.X;
Position.X = GraphicsViewport.Width - Texture.Width;
}
// Apply friction
Velocity -= Vector2(Math.Pow(Physics.Surfaces.Concrete.X, RemainingTime),
Math.Pow(Physics.Surfaces.Concrete.Y, RemainingTime))
* Velocity;
// Invert velocity
Velocity.X = -Velocity.X;
Position.X = Position.X + Velocity.X * RemainingTime;
}
if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
{
// We've hit a horizontal boundary
if (Position.Y < 0)
{
RemainingTime = -Position.Y / Velocity.Y;
Position.Y = 0;
}
else
{
RemainingTime = (Position.Y - (GraphicsViewport.Height - Texture.Height)) / Velocity.Y;
Position.Y = GraphicsViewport.Height - Texture.Height;
}
// Remove excess gravity
Velocity.Y -= RemainingTime * Physics.Gravity.Force;
// Apply friction
Velocity -= Vector2(Math.Pow(Physics.Surfaces.Grass.X, RemainingTime),
Math.Pow(Physics.Surfaces.Grass.Y, RemainingTime))
* Velocity;
// Invert velocity
Velocity.Y = -Velocity.Y;
// Re-add excess gravity
float OldVelocityY = Velocity.Y;
Velocity.Y += RemainingTime * Physics.Gravity.Force;
// If velocity changed sign again, clamp it to zero
if (Velocity.Y * OldVelocityY <= 0)
Velocity.Y = 0;
Position.Y = Position.Y + Velocity.Y * RemainingTime;
}
}
5. Diğer ilaveler
Daha iyi simülasyon kararlılığı için fizik simülasyonunuzu daha yüksek bir frekansta çalıştırmaya karar verebilirsiniz. Bu, yukarıdaki değişikliklerle önemsizdirTimeStep
, çünkü çerçevenizi istediğiniz kadar parçaya bölmeniz yeterlidir. Örneğin:
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
}