นี่คือขั้นตอนที่จำเป็นสำหรับการพัฒนาลูปจำลองฟิสิกส์ของคุณ
1. การประทับเวลา
ปัญหาหลักที่ฉันเห็นด้วยรหัสของคุณก็คือมันไม่ได้คำนึงถึงขั้นตอนทางฟิสิกส์ ควรชัดเจนว่ามีสิ่งผิดปกติเกิดขึ้นPosition += Velocity;
เนื่องจากหน่วยไม่ตรงกัน ไม่ว่าVelocity
จะเป็นความเร็วหรือสิ่งที่ขาดหายไป
แม้ว่าค่าความเร็วและแรงโน้มถ่วงของคุณจะถูกปรับอัตราส่วนให้แต่ละเฟรมเกิดขึ้นที่หน่วยเวลา1
(หมายความว่าเช่น Velocity
หมายถึงระยะทางที่เดินทางในหนึ่งวินาที) เวลาจะต้องปรากฏที่ใดที่หนึ่งในรหัสของคุณโดยปริยาย (โดยการแก้ไขตัวแปรเพื่อให้ ชื่อของพวกเขาสะท้อนให้เห็นถึงสิ่งที่พวกเขาเก็บจริง ๆ ) หรืออย่างชัดเจน (โดยการแนะนำการประทับเวลา) ฉันเชื่อว่าสิ่งที่ง่ายที่สุดที่จะทำคือการประกาศหน่วยเวลา:
float TimeStep = 1.0;
และใช้ค่านั้นในทุกที่ที่ต้องการ:
Velocity += Physics.Gravity.Force * TimeStep;
Position += Velocity * TimeStep;
...
โปรดทราบว่าคอมไพเลอร์ที่เหมาะสมจะลดความซับซ้อนของการคูณด้วย1.0
ดังนั้นส่วนนั้นจะไม่ทำให้ช้าลง
ตอนนี้Position += Velocity * TimeStep
ยังไม่แน่นอน (ดูคำถามนี้เพื่อทำความเข้าใจว่าทำไม) แต่ตอนนี้อาจจะทำได้
นอกจากนี้ต้องคำนึงถึงเวลาด้วย:
Velocity *= Physics.Air.Resistance;
มันค่อนข้างยากที่จะแก้ไข วิธีหนึ่งที่เป็นไปได้คือ:
Velocity -= Vector2(Math.Pow(Physics.Air.Resistance.X, TimeStep),
Math.Pow(Physics.Air.Resistance.Y, TimeStep))
* Velocity;
2. อัพเดตสองเท่า
ตอนนี้ตรวจสอบสิ่งที่คุณทำเมื่อตีกลับ (แสดงเฉพาะรหัสที่เกี่ยวข้อง):
Position += Velocity * TimeStep;
if (Position.Y < 0)
{
Velocity.Y = -Velocity.Y * Physics.Surfaces.Grass;
Position.Y = Position.Y + Velocity.Y * TimeStep;
}
คุณจะเห็นว่าTimeStep
มีการใช้สองครั้งระหว่างการเด้ง นี่เป็นการทำให้ลูกบอลมีเวลามากขึ้นสองเท่าในการอัพเดทตัวเอง นี่คือสิ่งที่ควรเกิดขึ้นแทน:
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. แรงโน้มถ่วง
ตรวจสอบส่วนนี้ของรหัสตอนนี้:
if(Position.Y < GraphicsViewport.Height - Texture.Height)
{
Velocity += Physics.Gravity.Force * TimeStep;
}
คุณเพิ่มแรงโน้มถ่วงตลอดระยะเวลาของเฟรม แต่ถ้าลูกบอลตีกลับระหว่างกรอบจริงล่ะ จากนั้นความเร็วจะกลับด้าน แต่แรงโน้มถ่วงที่เพิ่มเข้ามาจะทำให้ลูกบอลเร่งความเร็วจากพื้นดิน! ดังนั้นแรงโน้มถ่วงส่วนเกินจะต้องถูกลบออกเมื่อตีกลับจากนั้นเพิ่มอีกครั้งในทิศทางที่ถูกต้อง
อาจเกิดขึ้นได้แม้การเพิ่มแรงโน้มถ่วงในทิศทางที่ถูกต้องจะทำให้ความเร็วในการเร่งความเร็วมากเกินไป เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถข้ามการเพิ่มแรงโน้มถ่วง (หลังจากทั้งหมดมันไม่มากและเพียงแค่เฟรม) หรือยึดความเร็วเป็นศูนย์
4. รหัสคงที่
และนี่คือรหัสที่ได้รับการอัปเดตอย่างสมบูรณ์:
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. การเพิ่มเติมเพิ่มเติม
เพื่อความเสถียรของการจำลองที่ได้รับการปรับปรุงคุณอาจตัดสินใจใช้การจำลองทางฟิสิกส์ด้วยความถี่ที่สูงขึ้น นี้จะทำเล็ก ๆ น้อย ๆจากการเปลี่ยนแปลงดังกล่าวข้างต้นเกี่ยวข้องTimeStep
เพราะคุณเพียงแค่ต้องแยกกรอบของคุณในขณะที่ชิ้นมากที่สุดเท่าที่คุณต้องการ ตัวอย่างเช่น
public void Update()
{
float TimeStep = 1.0;
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
Update(TimeStep / 4);
}