C # XNA - Физика мяча в стиле Breakout

Я создаю проект для университетского задания и наткнулся на небольшую загвоздку. Проблема незначительна и, скорее всего, может быть выдана незамеченной, но меня она все еще беспокоит, и, будучи процедурным человеком с точки зрения выполнения работы, я не могу перейти ни к чему другому, пока она не будет решена.

У меня есть код ракетки и мяча, работающий нормально, а также простая угловая скорость, основанная на том, где мяч ударяется о ракетку, однако моя проблема заключается в том, когда мяч ударяется о кирпичи. Код в основном работает и кажется безупречным, однако это меняется, когда мяч выполняет одно из этих двух действий:

  • Попадание кирпича в один из углов может изменить неверную ось скорости мяча.
  • Удар по двум кирпичам одновременно (по их углам) заставит мяч пройти через них и удалит их; даже если у них полное здоровье.

Код, который я использую, находится здесь (Ball.cs):

ИЗМЕНИТЬ (обновленный код):

public bool Touching(Brick brick)
{
    var position = Position + Velocity;
    return position.Y + Size.Y >= brick.Position.Y &&
              position.Y <= brick.Position.Y + brick.Size.Y &&
              position.X + Size.X >= brick.Position.X &&
              position.X <= brick.Position.X + brick.Size.X;
}

public bool Collide(Brick brick)
{
    //Secondary precaution check
    if (!Touching(brick)) return false;

    //Find out where the ball will be
    var position = Position + Velocity;

    //Get the bounds of the ball based on where it will be
    var bounds = new Rectangle((int)position.X, (int)position.Y, Texture.Width, Texture.Height);

    //Stop the ball from moving here, so that changes to velocity will occur afterwards.
    Position = Position - Velocity;

    //If the ball hits the top or bottom of the brick
    if (bounds.Intersects(brick.Top) || bounds.Intersects(brick.Bottom))
    {
        Velocity = new Vector2(Velocity.X, -Velocity.Y); //Reverse the Y axe of the Velocity
    }

    //If the ball hits the left or right of the brick
    if (bounds.Intersects(brick.Left) || bounds.Intersects(brick.Right))
    {
        Velocity = new Vector2(-Velocity.X, Velocity.Y); //Reverse the X axe of the Velocity
    }

    return true;
}

Эти методы вызываются из метода Update Level.cs:

public override void Update(GameTime gameTime)
{
    player.Update(gameTime);
    balls.ForEach(ball => ball.Update(gameTime));
    foreach (var brick in bricks)
    {
        foreach (var ball in balls)
        {
            if (ball.Touching(brick))
            {
                if (!collidingBricks.Contains(brick)) collidingBricks.Add(brick);
            }
        }
        brick.Update(gameTime);
    }

    if (collidingBricks.Count > 0)
    {
        foreach (var ball in balls)
        {
            Brick closestBrick = null;
            foreach (var brick in collidingBricks)
            {
                if (closestBrick == null)
                {
                    closestBrick = brick;
                    continue;
                }
                if (Vector2.Distance(ball.GetCenterpoint(brick), ball.GetCenterpoint()) < Vector2.Distance(ball.GetCenterpoint(closestBrick), ball.GetCenterpoint()))
                {
                    closestBrick = brick;
                }else
                {
                    brick.Health--;
                    if (brick.Health > 0) brick.Texture = Assets.GetBrick(brick.TextureName, brick.Health);
                }
            }

            if (ball.Collide(closestBrick))
            {
                closestBrick.Health--;
                if (closestBrick.Health > 0) closestBrick.Texture = Assets.GetBrick(closestBrick.TextureName, closestBrick.Health);
            }
        }
        collidingBricks = new List<Brick>();
    }

    bricks.RemoveAll(brick => brick.Health <= 0);
    base.Update(gameTime);
}

Метод столкновения мяча - это то, что определяет новые значения скорости мяча. Как видите, это полностью зависит от того, где будет находиться мяч, а не от того, где он находится. Кроме того, я запускаю проверку столкновений для ближайшего кирпича, с которым сталкивается мяч, но уменьшаю здоровье всех кирпичей, которых касается мяч (поэтому скорость будет обновляться только на основе только ближайший кирпич, а не все сталкивающиеся кирпичи).

Я понимаю, почему это вызывает у меня проблемы, и что это может быть из-за того, что мяч одновременно ударяет по двум сторонам (при попадании в угол).

Как я уже сказал, это незначительная проблема, но она все равно может повлиять на игровой процесс, особенно если вы ожидаете, что мяч будет двигаться в одном направлении, но только в противоположном.


person Dragonphase    schedule 19.11.2013    source источник
comment
Если вы return true; после проверки столкновения Y, разве вы не отдаете предпочтение этому направлению в случае, если оно столкнется по обеим осям?   -  person Cyral    schedule 20.11.2013
comment
Вы правы в том, что он все еще в некоторой степени поддерживает направление, и я мог бы просто вернуть true после обоих операторов if, а не возвращать false. Однако это все еще вызывает проблемы при определении скорости мяча относительно его текущей траектории.   -  person Dragonphase    schedule 20.11.2013


Ответы (2)


Я предполагаю, что каждый вызов вашего Update метода вы перемещаете мяч в новую позицию, добавляя вектор скорости, масштабированный по времени кадра, а затем проверяете новую позицию. Это правильно?

В этой модели мяч занимает ряд точек, не проходя через промежуточное пространство. Этот тип движения вызывает множество проблем из-за эффекта, называемого «туннелирование». Это происходит, когда объект движется достаточно быстро, и кажется, что он частично или полностью проходит сквозь объекты.

Представьте, что ваш мяч движется вертикально со скоростью 3 пикселя за кадр и в настоящее время находится на расстоянии 1 пикселя от края блока. В следующем кадре мяч переместится в позицию на 2 пикселя внутри блока. Поскольку мяч и блок не могут перекрываться, это ошибка. Возможно, это не огромная ошибка, но все же неверно.

Как вы обнаружили, ошибка действительно кусает вас, когда вы доходите до углов. Если новое положение мяча перекрывает угол блока, становится труднее определить правильное действие, поскольку ближайший край блока может не совпадать с тем, через который вы прошли (концептуально), чтобы добраться туда, где вы оказались.

Решением этой проблемы является вызов Continuous Collision Detection. Каждый раз, когда вы перемещаете мяч, вы должны проверять всю траекторию движения, чтобы определить, произошло ли столкновение.

Самый простой способ - хотя, конечно, не самый быстрый - может заключаться в использовании чего-то вроде Брезенхема. Алгоритм для определения позиций, которые ваш мяч занимает на линии движения. Bresenham's даст вам разумную линию движения с целыми шагами - один шаг на пиксель. Эти шаги переводятся в координаты, которые можно использовать для обнаружения столкновений.

Вы можете немного ускорить процесс, предварительно вычислив, возможно, чтобы столкновение произошло, и если да, то с чем. Если у вас на экране 100 блоков, вы не хотите проверять каждый из них для каждого шага. Вы можете сузить диапазон, вычислив прямоугольную ограничивающую рамку для общего движения (от текущей позиции до конечной позиции) и построив список всех блоков и других объектов, которые попадают в рамку. Отсутствие результатов означает отсутствие коллизий, так что это хороший способ сократить дорогостоящие операции, а также в конечном итоге сделать их менее затратными.

Есть много работы, которую нужно сделать, чтобы сделать его идеальным, и достаточно большой объем, чтобы подойти к нему достаточно близко. Google continuous collision detection и game physics tunneling для получения дополнительной информации по темам.

Если вам это сойдет с рук, что-нибудь вроде [Box2D] или Farseer предложит вам несколько интересных вариантов решения этой проблемы. Конечно, вы, вероятно, потратите столько же времени на переоснащение своей игры на физическом движке, сколько на решение исходной задачи: P

person Corey    schedule 20.11.2013
comment
Привет, Кори. Спасибо за Ваш ответ. Несмотря на то, что это всесторонний и отчасти понятный, я все еще борюсь с той же проблемой даже после применения непрерывного обнаружения столкновений. на основе CCD я создал новый Vector2 в методе столкновений, который представляет собой текущую позицию + текущую скорость. Проблема все еще сохраняется. Я дополню свой вопрос самым последним кодом. - person Dragonphase; 30.11.2013

Я думаю, что обе проблемы можно решить, разрешив столкновение по его мелкой оси. Рассмотрим эту ситуацию:

В вашем текущем коде это столкновение будет разрешено по оси Y, потому что для него есть предпочтение, хотя столкновение явно произошло по оси X.

Общий подход к столкновению состоит в том, чтобы сначала получить глубину столкновения (этого легко добиться, создав новый прямоугольник с помощью статического метода Rectangle.Intersect и используя его ширину и высоту). Затем вы должны проверить, насколько глубоко пересечение обеих осей, и, в вашем случае, перевернуть компонент мелкой оси вектора скорости мяча. Последний шаг - проверить взаимное расположение двух пересекающихся объектов и переместить один из них по мелкой оси - в данном примере шар находится справа от кирпича, поэтому его нужно переместить вправо на глубину пересечения.

person rot13    schedule 20.11.2013
comment
Извините за задержку с ответом. Я использовал этот ответ, чтобы вычислить, какую ось скорости мяча нужно изменить, и он отлично работает. Но я все еще сталкиваюсь с проблемой, когда мой мяч все еще пробивает длинную цепочку кирпичей, если попадает по два в обоих их углах. Я подумал о том, чтобы вычесть скорость кирпича из его положения после того, как он обнаружил удар, но до того, как скорость была изменена на обратную, однако это иногда приводит к зависанию мяча при ударе в угол и не всегда работает. - person Dragonphase; 27.11.2013
comment
@Dragonphase Вы пытались переместить мяч из перекрестка, используя его глубину? Это могло бы решить проблему, потому что кажется, что столкновение обнаруживается для обоих кирпичей, поэтому скорость меняется дважды. - person rot13; 28.11.2013
comment
Это действительно имеет смысл, я просто не могу поверить, что раньше не думал об этом ... - person Dragonphase; 30.11.2013