Dưới đây là các bước cần thiết để cải thiện vòng lặp mô phỏng vật lý của bạn.
1. Dấu thời gian
Vấn đề chính tôi có thể thấy với mã của bạn là nó không tính đến thời gian bước vật lý. Rõ ràng là có điều gì đó không đúng Position += Velocity;
vì các đơn vị không khớp. Hoặc Velocity
là thực sự không phải là một vận tốc, hoặc một cái gì đó bị thiếu.
Thậm chí nếu vận tốc và lực hấp dẫn giá trị của bạn được thu nhỏ như vậy mà mỗi khung xảy ra tại một đơn vị thời gian 1
(nghĩa là ví dụ. Velocity
Thực sự có nghĩa là khoảng cách đi du lịch trong một giây), thời gian phải xuất hiện ở đâu đó trong mã của bạn, hoặc là ngầm (bằng cách sửa chữa các biến để tên của họ phản ánh những gì họ thực sự lưu trữ) hoặc rõ ràng (bằng cách giới thiệu dấu thời gian). Tôi tin rằng điều dễ nhất để làm là khai báo đơn vị thời gian:
float TimeStep = 1.0;
Và sử dụng giá trị đó ở mọi nơi cần thiết:
Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...
Lưu ý rằng bất kỳ trình biên dịch tử tế nào cũng sẽ đơn giản hóa các phép nhân bằng cách đó 1.0
, vì vậy phần đó sẽ không làm mọi thứ chậm hơn.
Bây giờ Position += Velocity * TimeStep
vẫn chưa hoàn toàn chính xác (xem câu hỏi này để hiểu lý do tại sao) nhưng nó có thể sẽ làm cho bây giờ.
Ngoài ra, điều này cần phải mất thời gian vào tài khoản:
Velocity *= Physics.Air.Resistance;
Đó là một chút khó khăn hơn để sửa chữa; một cách có thể là:
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
Math.Pow(Physics.Air.Resistance.Y, TimeStep))
* Velocity;
2. Cập nhật kép
Bây giờ hãy kiểm tra những gì bạn làm khi nảy (chỉ có mã liên quan được hiển thị):
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Position.Y + Velocity.Y * TimeStep;
}
Bạn có thể thấy rằng TimeStep
được sử dụng hai lần trong quá trình thoát. Điều này về cơ bản là cho bóng gấp đôi thời gian để tự cập nhật. Đây là những gì nên xảy ra thay thế:
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. Trọng lực
Kiểm tra phần này của mã ngay bây giờ:
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
Bạn thêm trọng lực cho toàn bộ thời gian của khung. Nhưng nếu quả bóng thực sự nảy trong khung đó thì sao? Sau đó vận tốc sẽ được đảo ngược, nhưng trọng lực được thêm vào sau đó sẽ làm cho quả bóng tăng tốc ra khỏi mặt đất! Vì vậy, trọng lực vượt quá sẽ phải được loại bỏ khi nảy , sau đó thêm lại theo đúng hướng.
Nó có thể xảy ra rằng ngay cả việc thêm lại trọng lực theo đúng hướng sẽ khiến vận tốc tăng tốc quá nhiều. Để tránh điều này, bạn có thể bỏ qua phần bổ sung trọng lực (xét cho cùng, nó không nhiều và nó chỉ kéo dài một khung hình) hoặc tốc độ kẹp về không.
4. Mã cố định
Và đây là mã được cập nhật đầy đủ:
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. Bổ sung thêm
Để ổn định mô phỏng thậm chí được cải thiện, bạn có thể quyết định chạy mô phỏng vật lý của mình với tần suất cao hơn. Điều này được thực hiện tầm thường bởi những thay đổi liên quan ở trên TimeStep
, bởi vì bạn chỉ cần chia khung hình của mình thành nhiều phần như bạn muốn. Ví dụ:
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
}