Vật lý bóng: Làm mịn những cú nảy cuối cùng khi bóng đến phần còn lại


12

Tôi đã gặp phải một vấn đề khác trong trò chơi bóng nảy nhỏ của tôi.

Quả bóng của tôi đang nảy xung quanh tốt, ngoại trừ những giây phút cuối cùng khi nó sắp được nghỉ ngơi. Chuyển động của quả bóng là trơn tru cho phần chính, nhưng về cuối, quả bóng giật một lúc khi nó nằm dưới đáy màn hình.

Tôi có thể hiểu tại sao điều này xảy ra nhưng tôi dường như không thể làm nó trơn tru.

Tôi rất biết ơn về bất kỳ lời khuyên nào có thể được cung cấp.

Mã cập nhật của tôi là:

public void Update()
    {
        // Apply gravity if we're not already on the ground
        if(Position.Y < GraphicsViewport.Height - Texture.Height)
        {
            Velocity += Physics.Gravity.Force;
        }            
        Velocity *= Physics.Air.Resistance;
        Position += Velocity;

        if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)
        {
            // We've hit a vertical (side) boundary
            // Apply friction
            Velocity *= Physics.Surfaces.Concrete;

            // Invert velocity
            Velocity.X = -Velocity.X;
            Position.X = Position.X + Velocity.X;
        }

        if (Position.Y < 0 || Position.Y > GraphicsViewport.Height - Texture.Height)
        {
            // We've hit a horizontal boundary
            // Apply friction
            Velocity *= Physics.Surfaces.Grass;

            // Invert Velocity
            Velocity.Y = -Velocity.Y;
            Position.Y = Position.Y + Velocity.Y;
        }
    }

Có lẽ tôi cũng nên chỉ ra rằng Gravity, Resistance GrassConcretetất cả đều thuộc loại Vector2.


Chỉ cần xác nhận điều này: "ma sát" của bạn khi quả bóng chạm vào bề mặt có giá trị <1, về cơ bản hệ số bồi thường có đúng không?
Jorge Leitao

@ JCLeitão - Đúng.
Ste

Xin đừng thề tuân theo số phiếu khi bạn trao thưởng và trả lời đúng. Đi cho bất cứ điều gì đã giúp bạn.
aaaaaaaaaaaa

Đó là một cách tồi để xử lý tiền thưởng, về cơ bản, bạn đang nói rằng bạn không thể tự đánh giá bản thân mình nên bạn để những người ủng hộ quyết định ... Dù sao đi nữa, những gì bạn đang gặp phải là một sự va chạm phổ biến. Điều đó có thể được giải quyết bằng cách đặt một lượng xen kẽ tối đa, vận tốc tối thiểu hoặc bất kỳ dạng 'giới hạn' nào khác đạt được sẽ khiến thói quen của bạn dừng chuyển động và khiến đối tượng nghỉ ngơi. Bạn cũng có thể muốn thêm trạng thái nghỉ ngơi cho các đối tượng của mình để tránh việc kiểm tra vô ích.
Darkwings

@Darkwings - Tôi nghĩ rằng cộng đồng trong kịch bản này biết rõ hơn tôi về câu trả lời tốt nhất là gì. Đó là lý do tại sao các upvote sẽ ảnh hưởng đến quyết định của tôi. Rõ ràng, nếu tôi đã thử giải pháp với hầu hết các upvote và nó không giúp tôi, thì tôi sẽ không trao giải cho câu trả lời đó.
Ste

Câu trả lời:


19

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 Velocitylà 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 * TimeStepvẫ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);
}

"thời gian phải xuất hiện ở đâu đó trong mã của bạn." Bạn đang quảng cáo nhân 1 số ở khắp mọi nơi không chỉ là một ý tưởng hay, nó có bắt buộc không? Chắc chắn một dấu thời gian có thể điều chỉnh là một tính năng tốt, nhưng nó chắc chắn không bắt buộc.
aaaaaaaaaaaa

@eBusiness: đối số của tôi là nhiều hơn về tính nhất quán và phát hiện lỗi hơn là về dấu thời gian có thể điều chỉnh. Tôi không nói nhân với 1 là cần thiết, tôi đang nói velocity += gravitylà sai và chỉ có velocity += gravity * timestepý nghĩa. Cuối cùng, nó có thể cho kết quả tương tự, nhưng không có bình luận "Tôi biết tôi đang làm gì ở đây", nó vẫn có nghĩa là lỗi mã hóa, lập trình viên cẩu thả, thiếu kiến ​​thức về vật lý hoặc chỉ là mã nguyên mẫu cần phải được cải thiện.
sam hocevar

Bạn nói điều đó là sai , khi điều bạn cho là muốn nói là đó là thực tế tồi. Đó là ý kiến ​​chủ quan của bạn về vấn đề này, và thật tốt khi bạn thể hiện nó, nhưng nó mang tính chủ quan vì mã về vấn đề này thực hiện chính xác như ý của nó. Tất cả những gì tôi yêu cầu là bạn làm cho sự khác biệt giữa chủ quan và mục tiêu rõ ràng trong bài viết của bạn.
aaaaaaaaaaaa

2
@eBusiness: trung thực, nó sai bởi bất kỳ tiêu chuẩn lành mạnh. Đoạn mã không có gì làm như ý nghĩa của nó đối với tất cả, bởi vì 1) thêm vận tốc và lực hấp dẫn thực sự không có ý nghĩa gì; và 2) nếu nó mang lại một kết quả hợp lý thì đó là vì giá trị được lưu trữ trong gravitythực tế không phải là trọng lực. Nhưng tôi có thể làm cho điều đó rõ ràng hơn trong bài viết.
sam hocevar

Trái lại, gọi nó là sai theo bất kỳ tiêu chuẩn lành mạnh nào. Bạn đúng rằng trọng lực không được lưu trữ trong biến có tên là trọng lực, thay vào đó là một con số, và đó là tất cả những gì sẽ xảy ra, nó không có bất kỳ mối quan hệ nào với vật lý ngoài những gì chúng ta tưởng tượng ra, nhân nó với một số khác không thay đổi điều đó. Những gì nó dường như thay đổi là khả năng và / hoặc sự sẵn sàng của bạn để thực hiện kết nối tinh thần giữa mã và vật lý. Nhân tiện một quan sát tâm lý khá thú vị.
aaaaaaaaaaaa

6

Thêm một kiểm tra để ngăn chặn sự nảy, sử dụng vận tốc dọc tối thiểu. Và khi bạn có được độ nảy tối thiểu, đặt bóng xuống đất.

MIN_BOUNCE = <0.01 e.g>;

if( Velocity.Y < MIN_BOUNCE ){
    Velocity.Y = 0;
    Position.Y = <ground position Y>;
}

3
Tôi thích giải pháp này, nhưng tôi sẽ không giới hạn độ nảy cho trục Y. Tôi sẽ tính toán mức bình thường của máy va chạm tại điểm va chạm và kiểm tra xem cường độ của vận tốc va chạm có lớn hơn ngưỡng nảy hay không. Ngay cả khi thế giới của OP chỉ cho phép Y bị trả lại, những người dùng khác có thể tìm thấy một giải pháp tổng quát hơn hữu ích. (Nếu tôi không rõ ràng, hãy nghĩ đến việc nảy hai quả cầu lại với nhau tại một điểm ngẫu nhiên)
brandon

@brandon, tuyệt, nó nên hoạt động tốt hơn với bình thường.
Zhen

1
@ Sau đó, nếu bạn sử dụng bình thường của bề mặt, bạn có khả năng bạn có thể có bóng cuối cùng dính vào một bề mặt có bình thường không song song với trọng lực. Tôi sẽ thử và tính trọng lực vào tính toán nếu có thể.
Nic Foster

Không có giải pháp nào trong số các giải pháp này nên đặt bất kỳ vận tốc nào thành 0. Bạn chỉ giới hạn độ phản xạ trên mức bình thường của vectơ tùy thuộc vào ngưỡng nảy
brandon

1

Vì vậy, tôi nghĩ vấn đề tại sao điều này xảy ra là bóng của bạn đang đến gần một giới hạn. Về mặt toán học, quả bóng không bao giờ dừng lại trên bề mặt, nó tiếp cận bề mặt.

Tuy nhiên, trò chơi của bạn không sử dụng thời gian liên tục. Đó là một bản đồ, đang sử dụng một xấp xỉ với phương trình vi phân. Và sự gần đúng đó là không hợp lệ trong tình huống giới hạn này (bạn có thể, nhưng bạn sẽ phải thực hiện các bước thời gian nhỏ hơn và nhỏ hơn, mà tôi cho là không khả thi.

Về mặt vật lý, điều xảy ra là khi quả bóng ở rất gần bề mặt, nó sẽ dính vào nó nếu tổng lực dưới một ngưỡng nhất định.

Câu trả lời @Zhen sẽ ổn nếu hệ thống của bạn đồng nhất, không. Nó có một số trọng lực trên trục y.

Vì vậy, tôi sẽ nói rằng giải pháp sẽ không phải là vận tốc phải nằm dưới một ngưỡng nhất định, mà tổng lực tác dụng lên quả bóng sau khi cập nhật phải nằm dưới một ngưỡng nhất định.

Lực đó là sự đóng góp của lực tác dụng bởi bức tường lên quả bóng + trọng lực.

Điều kiện nên là một cái gì đó như

if (newVelocity + Vật lý.Gravity.Force <ngưỡng)

lưu ý rằng newVelocity.y là một đại lượng dương nếu độ nảy nằm trên tường bedd và trọng lực là một đại lượng âm.

Cũng lưu ý rằng newVelocity và Vật lý.Gravity.Force không có cùng kích thước, như bạn đã viết trong

Velocity += Physics.Gravity.Force;

có nghĩa là, giống như bạn, tôi giả sử rằng delta_time = 1 và ballMass = 1.

Hi vọng điêu nay co ich


1

Bạn có một bản cập nhật vị trí bên trong kiểm tra va chạm của bạn, nó là dư thừa và sai. Và nó bổ sung năng lượng cho quả bóng do đó có khả năng giúp nó di chuyển liên tục. Cùng với trọng lực không được áp dụng tại một số khung hình, điều này mang đến cho bạn sự chuyển động kỳ lạ. Gỡ bỏ nó.

Bây giờ bạn có thể thấy một vấn đề khác, đó là quả bóng bị "kẹt" bên ngoài khu vực được chỉ định, nảy liên tục qua lại.

Một cách đơn giản để giải quyết vấn đề này là kiểm tra xem bóng di chuyển đúng hướng trước khi thay đổi nó.

Vì vậy, bạn nên thực hiện:

if (Position.X < 0 || Position.X > GraphicsViewport.Width - Texture.Width)

Vào:

if ((Position.X < 0 && Velocity.X < 0) || (Position.X > GraphicsViewport.Width - Texture.Width && Velocity.X > 0))

Và tương tự cho hướng Y.

Để quả bóng dừng lại một cách độc đáo, bạn cần phải dừng trọng lực tại một số điểm. Việc thực hiện hiện tại của bạn đảm bảo rằng quả bóng sẽ luôn hồi sinh vì trọng lực không hãm nó miễn là nó ở dưới lòng đất. Bạn nên thay đổi để luôn luôn áp dụng trọng lực. Tuy nhiên, điều này dẫn đến quả bóng từ từ chìm xuống đất sau khi lắng xuống. Một cách khắc phục nhanh cho vấn đề này là, sau khi áp dụng trọng lực, nếu quả bóng ở dưới mức bề mặt và di chuyển xuống dưới, hãy dừng nó lại:

Velocity += Physics.Gravity.Force;
if(Position.Y > GraphicsViewport.Height - Texture.Height && Velocity.Y > 0)
{
    Velocity.Y = 0;
}

Những thay đổi trong tổng số sẽ cung cấp cho bạn một mô phỏng phong nha. Nhưng hãy lưu ý rằng nó vẫn là một mô phỏng rất đơn giản.


0

Có một phương thức biến đổi cho bất kỳ và tất cả các thay đổi vận tốc, sau đó trong phương thức đó, bạn có thể kiểm tra vận tốc được cập nhật để xác định xem nó có di chuyển đủ chậm để đặt nó ở trạng thái nghỉ không. Hầu hết các hệ thống vật lý mà tôi biết gọi đây là 'sự phục hồi'.

public Vector3 Velocity
{
    public get { return velocity; }
    public set
    {
        velocity = value;

        // We get the direction that gravity pulls in
        Vector3 GravityDirection = gravity;
        GravityDirection.Normalize();

        Vector3 VelocityDirection = velocity;
        VelocityDirection.Normalize();

        if ((velocity * GravityDirection).SquaredLength() < 0.25f)
        {
            velocity.Y = 0.0f;
        }            
    }
}
private Vector3 velocity;

Trong phương pháp trên, chúng tôi hạn chế nảy bất cứ khi nào nó dọc theo cùng trục với trọng lực.

Một cái gì đó khác để xem xét sẽ được phát hiện bất cứ khi nào một quả bóng va chạm với mặt đất và nếu nó di chuyển khá chậm tại thời điểm va chạm, hãy đặt vận tốc dọc theo trục trọng lực về không.


Tôi sẽ không downvote vì điều này là hợp lệ, nhưng câu hỏi là hỏi về ngưỡng nảy, không phải ngưỡng vận tốc. Chúng hầu như luôn luôn tách biệt theo kinh nghiệm của tôi bởi vì hiệu ứng của sự dao động trong quá trình nảy thường tách biệt với hiệu ứng tiếp tục tính toán vận tốc một khi trực quan ở trạng thái nghỉ.
brandon

Họ là một trong cùng một. Các động cơ vật lý, như Havok, hoặc PhysX, và cơ sở JigLibX phục hồi cơ bản về vận tốc tuyến tính (và vận tốc góc). Phương pháp này sẽ hoạt động cho bất kỳ và tất cả các chuyển động của quả bóng, bao gồm cả nảy. Trên thực tế, dự án cuối cùng mà tôi tham gia (LEGO Universe) đã sử dụng một phương pháp gần như giống hệt với phương pháp này để ngăn chặn việc tung đồng xu một khi chúng bị chậm lại. Trong trường hợp đó, chúng tôi không sử dụng vật lý động nên chúng tôi phải thực hiện thủ công thay vì để Havok chăm sóc nó cho chúng tôi.
Nic Foster

@NicFoster: Tôi bối rối, vì trong đầu tôi, một đối tượng có thể di chuyển rất nhanh theo chiều ngang và hầu như không theo chiều dọc trong trường hợp phương thức của bạn sẽ không kích hoạt. Tôi nghĩ OP sẽ muốn khoảng cách dọc được đặt thành 0 mặc dù chiều dài vận tốc là cao.
George Duckett

@GeorgeDuckett: Ah cảm ơn bạn, tôi đã hiểu nhầm câu hỏi ban đầu. OP không muốn bóng ngừng chuyển động, chỉ cần dừng chuyển động thẳng đứng. Tôi đã cập nhật câu trả lời cho tài khoản chỉ với tốc độ nảy.
Nic Foster

0

Một điều nữa: Bạn đang nhân với một hằng số ma sát. Thay đổi điều đó - hạ thấp hằng số ma sát nhưng thêm một mức hấp thụ năng lượng cố định khi nảy. Điều này sẽ làm ẩm những lần cuối nhanh hơn nhiều.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.