物理模拟球落入地面

Physics Simulation Ball falling into floor

过去几天,我一直在使用 MonoGame 和 C# 对球进行物理模拟。球会在重力作用下弹跳,但当球的弹跳太低时,球就会落到地板上。有什么办法可以阻止这种情况发生吗? 我已经尝试降低重力常数,改变碰撞的工作方式等,但似乎没有任何效果。 (我对使用图形还很陌生,所以简单的解释会很有帮助)

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Particle_Simulation
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Texture2D particleTexture;

        SpriteFont font;

        Rectangle particle;

        Vector2 velocity = new Vector2(4, 0);
        Vector2 acceleration = new Vector2(0, 0.25f);

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            //graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            //graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
            Content.RootDirectory = "Content";
        }
        
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            //Load your game content here
            particleTexture = Content.Load<Texture2D>("ball");
            font = Content.Load<SpriteFont>("textFont");
            particle = new Rectangle(200, 200, 60, 60);
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            //Add your update logic here
            velocity.X += acceleration.X;
            velocity.Y += acceleration.Y;
            
            particle.X += (int)velocity.X;
            particle.Y += (int)velocity.Y;

            if (particle.Top <= 0 || particle.Bottom >= GraphicsDevice.Viewport.Height)
                velocity.Y = -velocity.Y;
            if (particle.Left <= 0 || particle.Right >= GraphicsDevice.Viewport.Width)
                velocity.X = -velocity.X;

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            //Add your drawing code here
            spriteBatch.Begin();

            spriteBatch.Draw(particleTexture, particle, Color.White);
            spriteBatch.DrawString(font, "X: " + velocity.X.ToString() + " Y: " + velocity.Y.ToString(), new Vector2(0, 0), Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

这是碰撞检测的经典问题之一,碰撞发生在物体路径上的某个点,但物体移动得足够远以至于下一个刻度不能解决问题,留下物体 - 你的球在这种情况下 - 永远落在视口之外。

要解决这个问题,您不仅需要改变垂直速度,还需要让球从碰撞点反射回来。你已经在反弹速度矢量,你只需要添加位置反射。简单的方法是只取超调量并从限制中减去它,将球放回界内:

if (particle.Top < 0)
{
    velocity.Y = -velocity.Y;
    particle.Y = -particle.Y;
}
else if (particle.Bottom >= GraphicsDevice.ViewPort.Height)
{
    velocity.Y = -velocity.Y;
    particle.Y = GraphicsDevice.ViewPort.Height - (particle.Bottom - GraphicsDevice.ViewPort.Height + particle.Height);
}

对速度和位置的 X 部分执行相同的操作。在大多数情况下,即使是角落穿透,最终结果也应该没问题。

由于您只是反映整个速度,因此您会得到一些累积误差,这些误差会导致球在每次撞击后弹跳 'higher'。想象一个球笔直下落但目前刚好在表面上方。加速后,如果没有碰撞,球的速度(大致)是正确的。但是...如果碰撞发生在行进距离的 10% 处,那么只有一部分加速度会添加到该点的速度上,然后路径的其余部分将会减慢,因为垂直速度现在与加速度。在极端情况下 - 底部高度低,速度非常低且高 'gravity' - 你仍然可能最终将球嵌入地面。

这会使你的碰撞代码更难一些,但至少你不会因为加速错误的累积而导致球飞出屏幕。

从您拥有的加速度和速度代码开始,然后如果发生碰撞,您需要返回并在弄清楚碰撞发生的路径有多远之后再做一次。您可以从简单地将其用作比率开始,但如果您的目标是真正的准确性,那么您需要做一些有趣的数学运算才能真正发挥作用。计算出撞击时的真实速度和反弹后重力的影响涉及一些微积分,但找到所有这些东西的参考应该不难。

您可能想要做的另一件事是在撞击后降低速度。完美的弹性碰撞在碰撞后给出相同的速度,但在现实世界中,物体的一些动量被浪费为热量、声音等。您可以通过对反向速度使用一个小的乘数来表示这一点 - 乘以 0.99 或其他东西 - 在碰撞中增加一些损失。

因为你几乎永远不会在你的失误中获得完美的平衡,球要么加速要么减速,最终逃脱或落在地板上。确保您有应对这两种情况的策略。

当你以任意角度发生碰撞时......数学又变得更有趣了。


哦,别忘了按 gameTime 缩放加速度值。 ElapsedGameTime 属性 会告诉您自上次调用 Update 以来已经过了多长时间,而且它会不一致。如果一帧需要很长时间来处理,那么球的加速度就会变慢。同样,较短的帧时间会使球移动得更快。