#ios #sprite-kit #game-physics #skphysicsbody
#iOS #sprite-kit #игра-физика #skphysicsbody
Вопрос:
Я испытываю огромное падение кадров в секунду в сцене с несколькими статическими SKSpriteNode
узлами, у которых тела определены с помощью SKPhysicsBody
init(polygonFrom: CGPath)
, и несколькими простыми динамическими SKSpriteNode
узлами с телами, определенными с помощью init(rectangleOf: CGSize)
.
Динамические узлы запускаются по всей сцене и в конечном итоге останавливаются, в зависимости от физики. Каждый динамический узел сталкивается с другими динамическими узлами, а также со статическими узлами, упомянутыми ранее.
Игра работает плавно со скоростью 60 кадров в секунду, пока количество динамических узлов на экране не достигнет ~ 30 или более. После этого частота кадров в секунду начинает резко падать примерно до 10 кадров в секунду.
Примечание 1: Проблема отсутствует на iOS 10, только на iOS 9 (я не тестировал iOS 8, поскольку я ее не поддерживаю).
Примечание 2: количество вызовов draw не увеличивается из-за увеличения количества динамических узлов, поэтому, похоже, на стороне OpenGL проблем нет.
Примечание 3: я отключил contactTestBitMask
, чтобы убедиться, что это не является причиной проблемы.
Я запустил инструменты с помощью Time Profiler, сфокусировался на сегменте, где падает FPS, и обнаружил следующую странную вещь:
Странно то, что более 50% времени в проблемном сегменте тратится на PhysicsKit
. Обратите внимание, что не имеет значения, происходят ли столкновения в данный момент или динамические узлы просто остаются на месте. Результаты всегда одни и те же.
Это должно быть причиной этого, но поскольку все это происходит в системных библиотеках ( PhysicsKit
), я действительно не имею представления, где искать проблему в моей кодовой базе.
Спасибо за любую помощь!
Комментарии:
1. Вы пробовали очищать проект? Shift-CMD-K
2. У SpriteKit всегда были очень странные несоответствия между версиями. Даже между небольшими релизами. Apple никогда не публикует изменения или другие полезные примечания к выпуску. Они редко отвечают на длинные темы о проблемах с производительностью на своих форумах. Я говорю вам это, чтобы вы не думали, что это полная аномалия. В последней версии SpriteKit есть сообщения об ошибках памяти и проблемах со звуком, которые должны были быть обнаружены базовыми модульными тестами и были частью iOS 10 с ранних бета-версий. Пользователей Sprite Kit не так много, и в Apple ему не уделяется особого внимания.
3. @Nik да, много раз.
4. Ну, если вы хотите, есть способ импортировать Box2D в SpriteKit.
5. @NSDawg Не уверен, хочу ли я это делать.
Ответ №1:
Внутри вашу проблему обойти невозможно, поскольку nateslager уже упоминал очевидные проблемы, я просто расскажу вам о решении.
Что вы делаете, так это разбиваете свою сцену на квадранты или более с некоторым перекрытием и резервируете categoryBitMask
s для этих квадрантов, поскольку физические объекты могут иметь более одной битмаски категории. Теперь вам нужно обновлять эти категории каждый кадр, поэтому я хотел бы переопределить свойство position и добавить didSet
, чтобы указать узлу, в каких квадрантах они находятся.
Теперь это может стать очень сложным в зависимости от того, как вы используете эти флаги, потому что вы не можете делать такие вещи, как quadrant4 amp; bad, потому что тогда все равно будет регистрироваться все хорошее и плохое. Вместо этого вам нужен quadrant4bad в качестве категории 1. Идея состоит в том, чтобы уменьшить количество проверок, которые выполняются между всеми телами.
Например, у нас есть сцена размером от (-5,-5) до (5,5) и размером (11,11)
квадрант1 равен (-5,0),(0,0),(-5,-5),(0,-5)
quadrant2 — это (0,0),(5,0),(0,-5),(5,-5)
квадрант3 равен (-5,5),(0,5),(-5,0),(0,0)
квадрант4 равен (5,0),(5,5),(0,0),(5,0)
Квадрант — это (-2.5,2.5),(2.5,2.5),(-2.5,-2.5),(2.5,2.5)
enum PhysicsCategory : UInt
{
case good = 0b1
case quadrant1Bad = 0b10
case quadrant2Bad = 0b100
case quadrant3Bad = 0b1000
case quadrant4Bad = 0b10000
case quadrantCBad = 0b100000
}
//we set up a good player at position 0,0 so he is in the center quadrant
good.contactBitMask = quadrantC
//we set up a enemy at position -5, -5 so he is in the first quadrant
bad1.contactBitMask = quadrant1
//we set up a enemy at position 5, -5 so he is in the second quadrant
bad2.contactBitMask = quadrant2
//we set up a enemy at position -5, 5 so he is in the third quadrant
bad3.contactBitMask = quadrant3
//we set up a enemy at position 5, 5 so he is in the fourth quadrant
bad4.contactBitMask = quadrant4
//we set up a enemy at position -2.5, 2.5 so he is in the third quadrant and center quadrant (remember, they have a width and height to them, so they will be in more than 1 quadrant
badC.contactBitMask = quadrant3 | quadrantC
Теперь, когда наша игра запущена, гуд будет проверять наличие врагов только в своем квадранте, поэтому проверка будет производиться только на quadrantC
Если good перейдет в квадрант3, то будут выполнены только 2 проверки, bad3 и badC
Это поможет вам уменьшить количество вызовов на серверной части физики, что должно привести к меньшей задержке.
Если у вас много разных категорий, которые вы используете, то я бы посоветовал вместо использования нескольких битовых масок отключать битовые маски категорий у ваших врагов, когда они не находятся в определенных квадрантах. Это также уменьшит количество проверок, выполняемых физическим движком.
Комментарии:
1. Спасибо за ваш ответ. Определенно хорошая идея, но, глядя на мою игру, я не уверен, что это поможет, поскольку цель состоит в том, чтобы динамические узлы размещались в определенных горячих точках на статических узлах. Поэтому я полагаю, что вычисления не сильно помогут. Я попробую это в качестве последнего средства и дам вам знать. Кстати, я быстро попытался отключить
categoryBitMasks
после того, как динамические узлы пришли в «состояние покоя», но это, похоже, не влияет на вычисления.2. если у вас есть статические узлы, затем объедините все ваши физические тела в одно тело и назначьте его частичному узлу, вы можете использовать ContactPoint, чтобы выяснить, какой узел был поражен
3. KnightOfDragon, как объединить несколько статических физических тел в одно физическое тело?
4. @classenApps это одна из особенностей физического тела
5. @claassenApps developer.apple.com/reference/spritekit/skphysicsbody /…
Ответ №2:
Даже если у вас есть только «несколько» физических тел многоугольника, каждому дополнительному физическому телу прямоугольной формы, независимо от того, движется оно или нет, требуется определенное расстояние от каждого физического тела многоугольника, вычисляемое в каждом кадре (отсюда функция b2Distance()). Попробуйте уменьшить сложность / количество ваших физических тел polygon, чтобы увеличить частоту кадров.
Я подозреваю, что внезапное падение производительности связано с тем, что кэш (b2SimplexCache), в котором хранятся эти расстояния, достигает максимальной емкости (возможно, ~ 30 значений), и в этот момент промахи кэша замедляют частоту кадров. iOS 10, должно быть, увеличила этот кэш. Другим решением было бы каким-то образом увеличить кэш для этой функции в iOS 9, хотя я не уверен, каким образом.
Комментарии:
1. Спасибо за ваш ответ, но я никак не мог бы упростить тела еще больше. В любом случае они не так сложны для статических узлов. А динамические узлы имеют только прямоугольные тела.
2. Сколько физических тел polygon вы используете? Что это такое?
3. Я использую около 4 физических тел polygon одновременно. Они применяются к
SKSpriteNode
узлам, которые представляют дома.4. При использовании всего четырех корпусов одновременно определенно присутствует какая-то ошибка, влияющая на вашу производительность. Можете ли вы увеличить количество тел в тестовом проекте в iOS 10, чтобы увидеть, когда он начнет демонстрировать такое же поведение / проблемы? @damirstuhec
5. Я тестировал на iOS 10 с 4 полигональными телами, и мне нужно было не менее 80 узлов с простым прямоугольным телом, чтобы начать видеть падение кадров. Падение было всего на несколько кадров, не такое существенное, как на iOS 9.