यहाँ आपके भौतिकी सिमुलेशन पाश में सुधार के लिए आवश्यक कदम हैं।
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);
}