#ios #scenekit #grand-central-dispatch
Вопрос:
Я только что узнал, что рендеринг SceneKit происходит в очереди рендеринга, которая не является основной последовательной очередью. Это отличается от SpriteKit, который обрабатывает рендеринг в основном потоке.
Сейчас у меня из-за этого состояние гонки. Мое сенсорное управление находится в основном потоке, а функция обновления для каждого кадра-в потоке рендеринга. Оба будут задавать положение для SCNNode.
Похоже, очередь рендеринга закрыта (я могу ошибаться), поэтому я не могу отправлять в нее сенсорные вызовы. Поэтому мне интересно, как обрабатывать касание в потоке рендеринга?
Ответ №1:
Touch исходит от UIViewController, GameControl-это класс, в котором я перемещаю объекты с помощью таймеров. Я использовал не только SpriteKit, поэтому не могу это комментировать, но, похоже, какой-то пример кода мог бы помочь…
класс GameViewController: UIViewController { …
func update(vTime: TimeInterval)
{
switch data.gameState
{
case .staging:
timersOn()
setMode(vState: .run)
gameStateManager.run()
gameStateManager.isStagingActive = true
}
break
case .nextWave:
if(gameStateManager.isNextWaveActive == false)
{
if(gameControl.nextWave() == false)
{
gameStateManager.endGame()
}
else
{
gameControl.resumeGame()
setMode(vState: .run)
gameStateManager.run()
}
gameStateManager.isNextWaveActive = true
}
break
case .run, .defenseAdd, .defenseUpgrade:
gameControl.updateTime(vTime: vTime)
break
}
@objc func handleTap(recognizer: UITapGestureRecognizer)
{
let location: CGPoint = recognizer.location(in: gameScene)
if(data.isAirStrikeModeOn == true)
{
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
gameControl.airStrike(position: scenePoint)
}
else
{
let hitResults = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
// May have selected an invalid panel or auto upgrade was on
if(gameControl.selectPanel(vPanel: vHit.node.name!) == false) { return }
return
}
}
}
}
extension GameViewController: SCNSceneRendererDelegate
{
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval)
{
update(vTime: time)
}
}
Таймеры находятся в игровом управлении — перемещают объекты, устанавливают узлы и т. Д.
Согласно комментариям, вот дополнительная информация:
SceneKit автоматически создает транзакцию всякий раз, когда вы изменяете объекты на графике сцены. Эта транзакция группирует любые дополнительные изменения, которые вы вносите в один и тот же поток во время текущей итерации цикла выполнения этого потока. Когда цикл выполнения повторяется в следующем цикле, SceneKit автоматически фиксирует транзакцию, атомарно применяя все изменения, внесенные во время транзакции, к графику сцены презентации (то есть версии графика сцены, отображаемой в данный момент).
Этот пост [31700071] дает представление о том, что вы можете/должны делать в основном потоке и как это влияет на цикл визуализации.
Комментарии:
1. Это будет иметь состояние гонки, потому
renderer
что иhandleTap
выполняются в разных потоках2. Всегда возможно, что я мог сделать это неправильно… но я использовал эту структуру для нескольких игр. Я построил симулятор, чтобы играть в игры, и проводил тесты без остановки в течение целой недели без остановки. Я также отслеживал сбои в реальном времени (нет) и статистику кадров в секунду. Я использую DispatchQueue.main.async (для включения таймеров), чтобы таймеры запускались в основном потоке. GameControl {} выполняет все манипуляции с узлами.
3. это небезопасно для потоков. он открыт для гоночных условий и может даже разбиться.
4. Это не мешает гонке… если вы не обновляете в основном потоке с помощью API закрытия (что-то вроде
update(_ oldPos: CGPoint)
), что не так. в противном случае прямое чтение этого сообщения определенно вызовет состояние гонки5. Ладно, извини, что не смог тебе помочь. Удачи.