Berikut langkah-langkah yang diperlukan untuk meningkatkan loop simulasi fisika Anda.
1. Timestep
Masalah utama yang bisa saya lihat dengan kode Anda adalah bahwa ia tidak memperhitungkan waktu langkah fisika. Harus jelas bahwa ada sesuatu yang salah dengan Position += Velocity;
unit tidak cocok. Entah Velocity
sebenarnya bukan kecepatan, atau ada sesuatu yang hilang.
Bahkan jika kecepatan dan nilai gravitasi Anda diskalakan sedemikian rupa sehingga setiap frame terjadi pada satuan waktu 1
(yang berarti bahwa mis. Velocity
Sebenarnya berarti jarak yang ditempuh dalam satu detik), waktu harus muncul di suatu tempat dalam kode Anda, baik secara implisit (dengan memperbaiki variabel sehingga nama mereka mencerminkan apa yang sebenarnya mereka simpan) atau secara eksplisit (dengan memperkenalkan stempel waktu). Saya percaya hal yang paling mudah untuk dilakukan adalah mendeklarasikan satuan waktu:
float TimeStep = 1.0;
Dan gunakan nilai itu di mana pun dibutuhkan:
Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...
Perhatikan bahwa kompiler yang layak akan menyederhanakan perkalian dengan 1.0
, sehingga bagian itu tidak akan membuat segalanya lebih lambat.
Sekarang Position += Velocity * TimeStep
masih belum sepenuhnya tepat (lihat pertanyaan ini untuk memahami mengapa) tetapi mungkin akan dilakukan untuk saat ini.
Juga, ini perlu mempertimbangkan:
Velocity *= Physics.Air.Resistance;
Agak sulit untuk diperbaiki; salah satu cara yang mungkin adalah:
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
Math.Pow(Physics.Air.Resistance.Y, TimeStep))
* Velocity;
2. Menggandakan pembaruan
Sekarang periksa apa yang Anda lakukan ketika memantul (hanya kode yang relevan ditampilkan):
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Position.Y + Velocity.Y * TimeStep;
}
Anda dapat melihat bahwa TimeStep
digunakan dua kali selama pentalan. Ini pada dasarnya memberi bola dua kali lebih banyak waktu untuk memperbarui sendiri. Inilah yang seharusnya terjadi:
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. Gravitasi
Periksa bagian kode ini sekarang:
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
Anda menambahkan gravitasi untuk seluruh durasi bingkai. Tetapi bagaimana jika bola benar-benar memantul selama bingkai itu? Maka kecepatan akan terbalik, tetapi gravitasi yang ditambahkan kemudian akan membuat bola berakselerasi dari tanah! Jadi kelebihan gravitasi harus dihilangkan ketika memantul , kemudian ditambahkan kembali ke arah yang benar.
Mungkin saja terjadi bahwa bahkan menambahkan kembali gravitasi ke arah yang benar akan menyebabkan kecepatan terlalu cepat. Untuk menghindari ini, Anda bisa melewati penambahan gravitasi (setelah semua, itu tidak banyak dan hanya berlangsung bingkai) atau menjepit kecepatan ke nol.
4. Kode tetap
Dan di sini adalah kode yang sepenuhnya diperbarui:
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. Penambahan lebih lanjut
Untuk stabilitas simulasi yang lebih baik, Anda dapat memutuskan untuk menjalankan simulasi fisika Anda pada frekuensi yang lebih tinggi. Ini dibuat sepele oleh perubahan-perubahan di atas yang melibatkan TimeStep
, karena Anda hanya perlu membagi frame Anda menjadi sebanyak yang Anda inginkan. Contohnya:
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
}