Подтвердить что ты не робот

С# XNA: Оптимизация обнаружения столкновений?

Я работаю над простой демонстрацией для обнаружения столкновений, которая содержит только кучу объектов, подпрыгивающих в окне. (Цель состоит в том, чтобы увидеть, сколько объектов может обрабатывать игра одновременно, не отбрасывая кадры.)

Существует гравитация, поэтому объекты либо движутся, либо сталкиваются со стеной.

Наивное решение было O (n ^ 2):

foreach Collidable c1:
      foreach Collidable c2:
             checkCollision(c1, c2);

Это довольно плохо. Поэтому я создал объекты CollisionCell, которые сохраняют информацию о части экрана. Идея состоит в том, что каждый Collidable должен только проверять наличие других объектов в своей ячейке. При использовании 60 пикселей на 60 пикселей, это дает почти 10-кратное улучшение, но я хотел бы нажать дальше.

Профайлер показал, что код тратит 50% своего времени в функции, которую каждая ячейка использует для получения своего содержимого. Вот он:

    // all the objects in this cell
    public ICollection<GameObject> Containing
    {
        get
        {
            ICollection<GameObject> containing = new HashSet<GameObject>();

            foreach (GameObject obj in engine.GameObjects) {
                // 20% of processor time spent in this conditional
                if (obj.Position.X >= bounds.X &&
                    obj.Position.X < bounds.X + bounds.Width &&
                    obj.Position.Y >= bounds.Y &&
                    obj.Position.Y < bounds.Y + bounds.Height) {

                    containing.Add(obj);
                }
            }

            return containing;
        }
    }

Из этого 20% времени программы тратится на это условное.

Здесь вызывается вызываемая функция:

    // Get a list of lists of cell contents
        List<List<GameObject>> cellContentsSet = cellManager.getCellContents();

        // foreach item, only check items in the same cell
        foreach (List<GameObject> cellMembers in cellContentsSet) {
            foreach (GameObject item in cellMembers) {
                 // process collisions
            }
        }


//...

    // Gets a list of list of cell contents (each sub list = 1 cell)
    internal List<List<GameObject>> getCellContents() {
        List<List<GameObject>> result = new List<List<GameObject>>();
        foreach (CollisionCell cell in cellSet) {
            result.Add(new List<GameObject>(cell.Containing.ToArray()));
        }
        return result;
    }

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

Что я могу сделать, чтобы оптимизировать это? (Кроме того, я новичок в С# - есть ли какие-либо другие вопиющие стилистические ошибки?)

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

ОБНОВЛЕНИЕ 1. Я решил сохранить список объектов, которые были обнаружены в ячейке при последнем обновлении, и сначала проверьте их, чтобы убедиться, что они все еще находятся в ячейке. Кроме того, я поддерживал area переменной CollisionCell, когда, когда ячейка была заполнена, я мог перестать смотреть. Вот моя реализация этого, и это сделало всю демо намного медленнее:

    // all the objects in this cell
    private ICollection<GameObject> prevContaining;
    private ICollection<GameObject> containing;
    internal ICollection<GameObject> Containing {
        get {
            return containing;
        }
    }

    /**
     * To ensure that `containing` and `prevContaining` are up to date, this MUST be called once per Update() loop in which it is used.
     * What is a good way to enforce this?
     */ 
    public void updateContaining()
    {
        ICollection<GameObject> result = new HashSet<GameObject>();
        uint area = checked((uint) bounds.Width * (uint) bounds.Height); // the area of this cell

        // first, try to fill up this cell with objects that were in it previously
        ICollection<GameObject>[] toSearch = new ICollection<GameObject>[] { prevContaining, engine.GameObjects };
        foreach (ICollection<GameObject> potentiallyContained in toSearch) {
            if (area > 0) { // redundant, but faster?
                foreach (GameObject obj in potentiallyContained) {
                    if (obj.Position.X >= bounds.X &&
                        obj.Position.X < bounds.X + bounds.Width &&
                        obj.Position.Y >= bounds.Y &&
                        obj.Position.Y < bounds.Y + bounds.Height) {

                        result.Add(obj);
                        area -= checked((uint) Math.Pow(obj.Radius, 2)); // assuming objects are square
                        if (area <= 0) {
                            break;
                        }
                    }
                }
            }
        }
        prevContaining = containing;
        containing = result;
   }

ОБНОВЛЕНИЕ 2 Я отказался от последнего подхода. Теперь я пытаюсь сохранить пул collidables (orphans) и удалять из них объекты, когда я нахожу ячейку, которая их содержит:

    internal List<List<GameObject>> getCellContents() {
        List<GameObject> orphans = new List<GameObject>(engine.GameObjects);
        List<List<GameObject>> result = new List<List<GameObject>>();
        foreach (CollisionCell cell in cellSet) {
            cell.updateContaining(ref orphans); // this call will alter orphans!
            result.Add(new List<GameObject>(cell.Containing)); 
            if (orphans.Count == 0) {
                break;
            }
        }
        return result;
    }

    // `orphans` is a list of GameObjects that do not yet have a cell
    public void updateContaining(ref List<GameObject> orphans) {
        ICollection<GameObject> result = new HashSet<GameObject>();

        for (int i = 0; i < orphans.Count; i++) {
            // 20% of processor time spent in this conditional
            if (orphans[i].Position.X >= bounds.X &&
                orphans[i].Position.X < bounds.X + bounds.Width &&
                orphans[i].Position.Y >= bounds.Y &&
                orphans[i].Position.Y < bounds.Y + bounds.Height) {

                result.Add(orphans[i]);
                orphans.RemoveAt(i);
            }
        }

        containing = result;
    }

Это дает лишь незначительное улучшение, а не 2x или 3x, которые я ищу.

ОБНОВЛЕНИЕ 3 Снова я отказался от вышеуказанных подходов и решил, чтобы каждый объект сохранял свою текущую ячейку:

    private CollisionCell currCell;
    internal CollisionCell CurrCell {
        get {
            return currCell;
        }
        set {
            currCell = value;
        }
    }

Это значение обновляется:

    // Run 1 cycle of this object
    public virtual void Run()
    {
        position += velocity;
        parent.CellManager.updateContainingCell(this);
    }

Код CellManager:

private IDictionary<Vector2, CollisionCell> cellCoords = new Dictionary<Vector2, CollisionCell>();
    internal void updateContainingCell(GameObject gameObject) {
        CollisionCell currCell = findContainingCell(gameObject);
        gameObject.CurrCell = currCell;
        if (currCell != null) {
            currCell.Containing.Add(gameObject);
        }
    }

    // null if no such cell exists
    private CollisionCell findContainingCell(GameObject gameObject) {

        if (gameObject.Position.X > GameEngine.GameWidth
            || gameObject.Position.X < 0
            || gameObject.Position.Y > GameEngine.GameHeight
            || gameObject.Position.Y < 0) {
            return null;
        }

        // we'll need to be able to access these outside of the loops
        uint minWidth = 0;
        uint minHeight = 0;

        for (minWidth = 0; minWidth + cellWidth < gameObject.Position.X; minWidth += cellWidth) ;
        for (minHeight = 0; minHeight + cellHeight < gameObject.Position.Y; minHeight += cellHeight) ;

        CollisionCell currCell = cellCoords[new Vector2(minWidth, minHeight)];

        // Make sure `currCell` actually contains gameObject
        Debug.Assert(gameObject.Position.X >= currCell.Bounds.X && gameObject.Position.X <= currCell.Bounds.Width + currCell.Bounds.X,
            String.Format("{0} should be between lower bound {1} and upper bound {2}", gameObject.Position.X, currCell.Bounds.X, currCell.Bounds.X + currCell.Bounds.Width));
        Debug.Assert(gameObject.Position.Y >= currCell.Bounds.Y && gameObject.Position.Y <= currCell.Bounds.Height + currCell.Bounds.Y,
            String.Format("{0} should be between lower bound {1} and upper bound {2}", gameObject.Position.Y, currCell.Bounds.Y, currCell.Bounds.Y + currCell.Bounds.Height));

        return currCell;
    }

Я думал, что это улучшится - теперь мне нужно только перебирать коллибельные, а не все ячейки collidables *. Вместо этого игра теперь отвратительно медленная, обеспечивая только 1/10 ее производительности с помощью моих подходов.

Профилировщик указывает, что теперь основным методом является другой метод, а время получения соседей для объекта тривиально короткое. Этот метод не изменился раньше, поэтому, возможно, я назову его ПУТЕМ больше, чем раньше...

4b9b3361

Ответ 1

В этой функции он тратит 50% своего времени, потому что вы много называете эту функцию. Оптимизация того, что одна функция даст только дополнительные улучшения производительности.

В качестве альтернативы просто вызовите функцию меньше!

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

Второй подход состоит в том, чтобы разбить цикл N * N на инкрементную форму и использовать бюджет ЦП.

Вы можете выделить бюджет ЦП для каждого из модулей, которые хотят действовать во время фрейма (во время обновлений). Столкновение - один из этих модулей, ИИ может быть другим.

Скажем, вы хотите запустить свою игру со скоростью 60 кадров в секунду. Это означает, что у вас есть около 1/60 с = 0,0167 с времени процессора для записи между кадрами. Нет, мы можем разделить эти 0,0167 с между нашими модулями. Пусть дает столкновение 30% бюджета: 0.005 с.

Теперь ваш алгоритм столкновения знает, что он может потратить всего 0,005 с. Поэтому, если у вас заканчивается время, для последующей работы потребуется отложить некоторые задачи - вы сделаете алгоритм инкрементальным. Код для достижения этого может быть простым:

const double CollisionBudget = 0.005;

Collision[] _allPossibleCollisions;
int _lastCheckedCollision;

void HandleCollisions() {

    var startTime = HighPerformanceCounter.Now;

    if (_allPossibleCollisions == null || 
        _lastCheckedCollision >= _allPossibleCollisions.Length) {

        // Start a new series
        _allPossibleCollisions = GenerateAllPossibleCollisions();
        _lastCheckedCollision = 0;
    }

    for (var i=_lastCheckedCollision; i<_allPossibleCollisions.Length; i++) {
        // Don't go over the budget
        if (HighPerformanceCount.Now - startTime > CollisionBudget) {
            break;
        }
        _lastCheckedCollision = i;

        if (CheckCollision(_allPossibleCollisions[i])) {
            HandleCollision(_allPossibleCollisions[i]);
        }
    }
}

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

Преимущества включают:

  • Алгоритм рассчитан на отсутствие времени, он просто возобновляется в следующем кадре, поэтому вам не нужно беспокоиться об этом конкретном случае.
  • Бюджетирование ЦП становится все более важным, так как увеличивается количество алгоритмов с расширенным/трудоемким временем. Думайте AI. Поэтому неплохо реализовать такую ​​систему на раннем этапе.
  • Время отклика человека меньше 30 Гц, ваша рамная петля работает на частоте 60 Гц. Это дает алгоритму 30 кадров для завершения его работы, поэтому он хорошо, что он не завершил свою работу.
  • Выполнение этого способа обеспечивает стабильную, независимую от данных частоту кадров.
  • Он по-прежнему пользуется оптимизацией производительности для самого алгоритма столкновения.
  • Алгоритмы столкновений предназначены для отслеживания "подкадра", в котором произошли столкновения. То есть, вам никогда не повезет, чтобы поймать столкновение так же, как это происходит, - думая, что вы это делаете, лжет себе.

Ответ 2

Я могу помочь здесь; Я написал свое собственное обнаружение столкновений в качестве эксперимента. Я думаю, что могу сказать вам прямо сейчас, что вы не получите нужную производительность без изменения алгоритмов. Конечно, наивный способ хорош, но работает только для множества предметов, прежде чем рушиться. Вам нужно Считать и обрезать. Основная идея идет так (из моего проекта библиотеки обнаружения столкновений):

using System.Collections.Generic;
using AtomPhysics.Interfaces;

namespace AtomPhysics.Collisions
{
    public class SweepAndPruneBroadPhase : IBroadPhaseCollider
    {
        private INarrowPhaseCollider _narrowPhase;
        private AtomPhysicsSim _sim;
        private List<Extent> _xAxisExtents = new List<Extent>();
        private List<Extent> _yAxisExtents = new List<Extent>();
        private Extent e1;

        public SweepAndPruneBroadPhase(INarrowPhaseCollider narrowPhase)
        {
            _narrowPhase = narrowPhase;
        }

        public AtomPhysicsSim Sim
        {
            get { return _sim; }
            set { _sim = null; }
        }
        public INarrowPhaseCollider NarrowPhase
        {
            get { return _narrowPhase; }
            set { _narrowPhase = value; }
        }
        public bool NeedsNotification { get { return true; } }


        public void Add(Nucleus nucleus)
        {
            Extent xStartExtent = new Extent(nucleus, ExtentType.Start);
            Extent xEndExtent = new Extent(nucleus, ExtentType.End);
            _xAxisExtents.Add(xStartExtent);
            _xAxisExtents.Add(xEndExtent);
            Extent yStartExtent = new Extent(nucleus, ExtentType.Start);
            Extent yEndExtent = new Extent(nucleus, ExtentType.End);
            _yAxisExtents.Add(yStartExtent);
            _yAxisExtents.Add(yEndExtent);
        }
        public void Remove(Nucleus nucleus)
        {
            foreach (Extent e in _xAxisExtents)
            {
                if (e.Nucleus == nucleus)
                {
                    _xAxisExtents.Remove(e);
                }
            }
            foreach (Extent e in _yAxisExtents)
            {
                if (e.Nucleus == nucleus)
                {
                    _yAxisExtents.Remove(e);
                }
            }
        }

        public void Update()
        {
            _xAxisExtents.InsertionSort(comparisonMethodX);
            _yAxisExtents.InsertionSort(comparisonMethodY);
            for (int i = 0; i < _xAxisExtents.Count; i++)
            {
                e1 = _xAxisExtents[i];
                if (e1.Type == ExtentType.Start)
                {
                    HashSet<Extent> potentialCollisionsX = new HashSet<Extent>();
                    for (int j = i + 1; j < _xAxisExtents.Count && _xAxisExtents[j].Nucleus.ID != e1.Nucleus.ID; j++)
                    {
                        potentialCollisionsX.Add(_xAxisExtents[j]);
                    }
                    HashSet<Extent> potentialCollisionsY = new HashSet<Extent>();
                    for (int j = i + 1; j < _yAxisExtents.Count && _yAxisExtents[j].Nucleus.ID != e1.Nucleus.ID; j++)
                    {
                        potentialCollisionsY.Add(_yAxisExtents[j]);
                    }

                    List<Extent> probableCollisions = new List<Extent>();
                    foreach (Extent e in potentialCollisionsX)
                    {
                        if (potentialCollisionsY.Contains(e) && !probableCollisions.Contains(e) && e.Nucleus.ID != e1.Nucleus.ID)
                        {
                            probableCollisions.Add(e);
                        }
                    }
                    foreach (Extent e2 in probableCollisions)
                    {
                        if (e1.Nucleus.DNCList.Contains(e2.Nucleus) || e2.Nucleus.DNCList.Contains(e1.Nucleus))
                            continue;
                        NarrowPhase.DoCollision(e1.Nucleus, e2.Nucleus);
                    }
                }
            }
        }

        private bool comparisonMethodX(Extent e1, Extent e2)
        {
            float e1PositionX = e1.Nucleus.NonLinearSpace != null ? e1.Nucleus.NonLinearPosition.X : e1.Nucleus.Position.X;
            float e2PositionX = e2.Nucleus.NonLinearSpace != null ? e2.Nucleus.NonLinearPosition.X : e2.Nucleus.Position.X;
            e1PositionX += (e1.Type == ExtentType.Start) ? -e1.Nucleus.Radius : e1.Nucleus.Radius;
            e2PositionX += (e2.Type == ExtentType.Start) ? -e2.Nucleus.Radius : e2.Nucleus.Radius;
            return e1PositionX < e2PositionX;
        }
        private bool comparisonMethodY(Extent e1, Extent e2)
        {
            float e1PositionY = e1.Nucleus.NonLinearSpace != null ? e1.Nucleus.NonLinearPosition.Y : e1.Nucleus.Position.Y;
            float e2PositionY = e2.Nucleus.NonLinearSpace != null ? e2.Nucleus.NonLinearPosition.Y : e2.Nucleus.Position.Y;
            e1PositionY += (e1.Type == ExtentType.Start) ? -e1.Nucleus.Radius : e1.Nucleus.Radius;
            e2PositionY += (e2.Type == ExtentType.Start) ? -e2.Nucleus.Radius : e2.Nucleus.Radius;
            return e1PositionY < e2PositionY;
        }
        private enum ExtentType { Start, End }
        private sealed class Extent
        {
            private ExtentType _type;
            public ExtentType Type
            {
                get
                {
                    return _type;
                }
                set
                {
                    _type = value;
                    _hashcode = 23;
                    _hashcode *= 17 + Nucleus.GetHashCode();
                }
            }
            private Nucleus _nucleus;
            public Nucleus Nucleus
            {
                get
                {
                    return _nucleus;
                }
                set
                {
                    _nucleus = value;
                    _hashcode = 23;
                    _hashcode *= 17 + Nucleus.GetHashCode();
                }
            }

            private int _hashcode;

            public Extent(Nucleus nucleus, ExtentType type)
            {
                Nucleus = nucleus;
                Type = type;
                _hashcode = 23;
                _hashcode *= 17 + Nucleus.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                return Equals(obj as Extent);
            }
            public bool Equals(Extent extent)
            {
                if (this.Nucleus == extent.Nucleus)
                {
                    return true;
                }
                return false;
            }
            public override int GetHashCode()
            {
                return _hashcode;
            }
        }
    }
}

и здесь код, который выполняет сортировку вставки (более или менее прямой перевод псевдокода здесь):

/// <summary>
/// Performs an insertion sort on the list.
/// </summary>
/// <typeparam name="T">The type of the list supplied.</typeparam>
/// <param name="list">the list to sort.</param>
/// <param name="comparison">the method for comparison of two elements.</param>
/// <returns></returns>
public static void InsertionSort<T>(this IList<T> list, Func<T, T, bool> comparison)
{
    for (int i = 2; i < list.Count; i++)
    {
        for (int j = i; j > 1 && comparison(list[j], list[j - 1]); j--)
        {
            T tempItem = list[j];
            list.RemoveAt(j);
            list.Insert(j - 1, tempItem);
        }
    }
}

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

Другая вещь, которую я хочу напомнить вам, заключается в том, что вы должны использовать профилировщик, например, тот, который сделан Red Gate. Там бесплатная пробная версия, которая должна длиться достаточно долго.

Ответ 3

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

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

Сообщите мне, если это имеет смысл, или поиск google/bing для "Quad Tree", или если вы не против использования открытого исходного кода, посмотрите эту удивительную библиотеку физики: http://www.codeplex.com/FarseerPhysics

Ответ 4

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

Я бы получил все мои объекты, которые можно скопировать в массив с нумерованным индексом. Это дает возможность воспользоваться наблюдением: если вы полностью перечислите список для каждого элемента, вы будете дублировать усилия. То есть (и обратите внимание, что я составляю имена переменных только для того, чтобы было проще выплюнуть некоторый псевдокод)

 if (objs[49].Intersects(objs[51]))

эквивалентно:

 if (objs[51].Intersects(objs[49]))

Итак, если вы используете нумерованный индекс, вы можете сэкономить некоторое время, не дублируя усилия. Сделайте это вместо:

for (int i1 = 0; i1 < collidables.Count; i1++)
{
    //By setting i2 = i1 + 1 you ensure an obj isn't checking collision with itself, and that objects already checked against i1 aren't checked again. For instance, collidables[4] doesn't need to check against collidables[0] again since this was checked earlier.
    for (int i2 = i1 + 1; i2 < collidables.Count; i2++)
    {
        //Check collisions here
    }
}

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

Ответ 5

Просто голова: некоторые люди предлагают дальнего; которая является большой 2D-библиотекой физики для использования с XNA. Если вы находитесь на рынке 3D-движка для XNA, я использовал bulletx (aС# port bullet) в проектах XNA с большим эффектом.

Примечание. У меня нет привязки к проектам bullet или bulletx.

Ответ 6

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

Но я бы заменил чек

if (obj.Position.X ....)

С

if (obj.Bounds.IntersercsWith(this.Bounds))

И я бы также заменил строку

result.Add(new List<GameObject>(cell.Containing.ToArray()));

Для

result.Add(new List<GameObject>(cell.Containing));

Поскольку свойство Containing возвращает ICollection<T> и наследует IEnumerable<T>, принятый конструктором List<T>.

И метод ToArray() просто повторяет список, возвращающий массив, и этот процесс выполняется снова при создании нового списка.

Ответ 7

Я знаю, что эта тема устарела, но я бы сказал, что отмеченный ансвар был совершенно неправильным...

его код содержит фатальную ошибку и не влияет на производительность, это будет действовать!

Сначала немного знака...

Его код создан так, что вы должны называть этот код в методе Draw, но это не то место для обнаружения конфликтов. В вашем методе рисования вы не должны ничего больше рисовать!

Но вы не можете вызвать HandleCollisions() в Update, потому что Update получает много вызовов, чем Draw's.

Если вы хотите вызвать HandleCollisions(), ваш код должен выглядеть так: этот код предотвратит обнаружение вашего столкновения более одного раза за кадр.

private bool check = false;

protected override Update(GameTime gameTime)
{
    if(!check)
    {    
        check = true;
        HandleCollisions();
    }
}

protected override Draw(GameTime gameTime)
{
    check = false;
}

Теперь посмотрим, что не так с HandleCollisions().

Пример: у нас есть 500 объектов, и мы проверили бы все возможные столкновения, не оптимизируя наше обнаружение.

С 500 объектами мы должны иметь 249500 проверок столкновений (499X500, потому что мы не хотим проверять, сталкивается ли объект с ним)

Но с приведенным ниже кодом Фрэнка мы потеряем 99,998% ваших коллизий (будет проведено всего 500 проверок столкновения). & Л; < ЭТО БУДЕТ УВЕЛИЧИТЬ ПЕРФОРМЫ!

Почему? Поскольку _lastCheckedCollision никогда не будет одинаковым или большим, чем allPossibleCollisions.Length... и из-за этого вы только проверите последний индекс 499

for (var i=_lastCheckedCollision; i<_allPossibleCollisions.Length; i++)
    _lastCheckedCollision = i;
//<< This could not be the same as _allPossibleCollisions.Length,
//because i have to be lower as _allPossibleCollisions.Length

вам нужно заменить это

if (_allPossibleCollisions == null || 
    _lastCheckedCollision >= _allPossibleCollisions.Length)

с этим

if (_allPossibleCollisions == null || 
    _lastCheckedCollision >= _allPossibleCollisions.Length - 1) {

чтобы весь ваш код мог быть заменен этим.

private bool check = false;

protected override Update(GameTime gameTime)
{
    if(!check)
    {    
        check = true;
        _allPossibleCollisions = GenerateAllPossibleCollisions();
        for(int i=0; i < _allPossibleCollisions.Length; i++)
        {
            if (CheckCollision(_allPossibleCollisions[i]))
            {
                //Collision!
            }
        }
    }
}

protected override Draw(GameTime gameTime)
{
    check = false;
}

... это должно быть намного быстрее, чем ваш код... и он работает: D...

Ответ RCIX должен быть отмечен как правильный, потому что анкенс Фрэнка ошибочен.

Ответ 8

Идея может заключаться в использовании ограничивающего круга. В принципе, когда создается Collidable, отслеживайте его центральную точку и вычисляйте радиус/диаметр, содержащий весь объект. Затем вы можете выполнить удаление первого прохода, используя что-то вроде:

int r = C1.BoundingRadius + C2.BoundingRadius;

if( Math.Abs(C1.X - C2.X) > r && Math.Abs(C1.Y - C2.Y) > r )
/// Skip further checks...

Это уменьшает сравнение с двумя для большинства объектов, но насколько это наберет вас. Я не уверен... профиль!