C # XNA: Оптимизация обнаружения столкновений? - PullRequest
16 голосов
/ 26 февраля 2010

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

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

Наивным решением было 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;
    }

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

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

Когда игра начинает отставать, объекты, как правило, упаковываются довольно плотно, поэтому не происходит большого движения. Возможно, я смогу как-то воспользоваться этим, написав функцию, чтобы увидеть, может ли, учитывая текущую скорость объекта, покинуть свою текущую ячейку до следующего вызова 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 Я отказался от этого последнего подхода. Сейчас я пытаюсь сохранить пул объектов для сопоставления (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;
    }

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

ОБНОВЛЕНИЕ 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;
    }

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

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

Ответы [ 8 ]

12 голосов
/ 26 февраля 2010

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

Либо просто вызовите функцию меньше!

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

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

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

Допустим, вы хотите запустить свою игру со скоростью 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]);
        }
    }
}

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

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

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

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

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 . Есть бесплатная пробная версия, которая должна длиться достаточно долго.

5 голосов
/ 26 февраля 2010

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

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

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

1 голос
/ 01 марта 2010

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

Примечание: я не имею никакого отношения к проектам bulletx или bulletx.

1 голос
/ 01 марта 2010

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

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

 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, то нет необходимости проверять наличие коллизий.

0 голосов
/ 16 апреля 2013

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

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

Сначала немного заметим ...

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

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

Если вы хотите вызвать 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 проверок коллизий). << ЭТО УВЕЛИЧИТ ЭФФЕКТИВНОСТЬ! </p>

Почему? Поскольку _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 должен быть помечен как правильный, потому что ответ Фрэнка неправильный.

0 голосов
/ 26 февраля 2010

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

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

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));

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

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

0 голосов
/ 26 февраля 2010

Идея может заключаться в использовании ограничивающего круга. По сути, когда создается 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...

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...