Comments 5
Спасибо! Не так много разработчиков игр на flutter, приятно видеть что не одинок.
Спасибо за статью об оптимизации столкновений на карте.
Работаю в аналогичной области, на другом языке.
Почитал ваш код SpatialGrid. Я правильно понял, что ему передается компонент, движение которого надо отслеживать?
SpatialGrid - хорошая тема, я использовал нечто подобное, только проще - двухмерный массив, спрятанный в классе Collider
у Collider по сути три основных метода, я нарасти больше, но основных три
- addObject(obj)
- updateObj(obj) - который обновляет позицию объекта
- getCollision(obj) - возвращает другой объект, с которым столкнулся наш obj, или null
Почитав ваш код, я понял, что массив ячеек можно заменить на Map, так как огромная часть ячеек не используется. Возможно, в будущем так и сделаю.
Collider прост, потому что пассивен - это забота обновляющегося объекта уведомить его об обновлении позиции. Мне нужно несколько двигающихся объектов, поэтому я решил пойти именно таким образом.
Почитал еще раз свой код - да, HashMap с ключом по координатам ячейки будет лучшим выбором, так позволяет освободиться от зависимости от заранее заданных размеров. Двухмерный массив хорош, но в коллизиях он пожалуй не нужен, уберу его оттуда совсем.
Я правильно понял, что ему передается компонент, движение которого надо отслеживать?
Да, почти так. Ну, я передаю ему не сам объект, а хитбокс объекта, для которого движок всё равно на каждом цикле будет расчитывать абсолютную позицию относительно мира, чтобы не делать эти вычисления повторно.
У SpatialGrid именно в том и суть, что мы можем отсекать большие группы объектов из работы над столкновениями, и не пробегаться по всем фрагментам игрового мира, а брать только активные. Поэтому тут однозначно HashMap, а не списки, т.к. предполагается, что ячеек очень-очень много и итерация по всем ним будет однозначно дольше, чем медленный доступ по ключу.
Хотя, я тут недавно разбирал, что можно в ряде случаев HashMap переписать на циклы, но в ходе профилирования у меня никогда не возникало случая, чтобы тормозил именно этот доступ по ключу. Уверен, на каком бы языке вы ни писали, это не станет узким местом. Уж меня Dart совсем не балует беспрецедентной скоростью работы, так что если бы в коде что-то тормозило - это точно сразу стало бы видно.
Но надо заметить, что всё зависит от игры: если у вас RPG, и в центре внимания персонаж, то можно замораживать или откладывать расчёты по фрагментам мира, которые находятся достаточно далеко от него. Если же это, скажем, RTS, то придётся одинаков скрупулёзно расчитывать события на всех участках карты - и тогда уже от итерации по всему списку никуда не деться.
да, у меня скорее стратегия, где возможно несколько движущихся юнитов
и множество статичных
В игровом движке Phaser, с которым я работаю, метод коллизий по умолчанию реализован как перебор массивов. Правда из плюсов - что добавлять к массивам, можно решить самому, не все спрайты мира запихиваются в эти массивы по умолчанию. Но я прикинул, что сотня объектов точно будет, а перебор 100^2 даже с минимальной нагрузкой - уже заметное время даже на хорошей декстопной системе. Поэтому озаботился концепцией разделения по площади.
С одиночным игроком в принципе проще. Но если он стреляет, как в Vampire Survivor (а это тоже реализовано на Phaser), все равно возникает множество движущихся объектов.
Полагаю, создатель Vampire Survivor тоже писал свою систему или ловко жонглировал массивами
Сотня объектов - это уже много для системы, которая определяет столкновения перебором всего. У меня и на десктопе тормозило на меньшем числе. пришлось сначала написать QuadTree, но потом и его стало мало. Так что тут однозначно надо как-то резать пространство.
Даже если все ячейки пространства придётся держать активными - всё равно будет буст непосредственно при расчёте столкновения, т.к. выборка объектов будет ограничена. То, что я у себя "выключаю" ячейки, которые далеко от игрока или не в зоне видимости - это уже из области "сопутствующей оптимизации", и непосредственно к столкновениям отношения не имеет. Просто раз появилась возможность переиспользовать сделанные вычисления для чего-то ещё - то почему бы и нет.
Flutter Flame: глобальная оптимизация производительности игрового движка