如何在 SceneKit 的渲染队列中处理触摸事件

Posted

技术标签:

【中文标题】如何在 SceneKit 的渲染队列中处理触摸事件【英文标题】:How to handle touch event in render queue in SceneKit 【发布时间】:2021-11-06 13:41:30 【问题描述】:

我刚刚发现 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)
    

计时器在 GameControl 中 - 移动对象、设置节点等。

每个 cmets,以下是附加信息:

每当您修改场景图中的对象时,SceneKit 都会自动创建一个事务。此事务将您在该线程的运行循环的当前迭代期间从同一线程所做的任何其他更改分组。当运行循环下一次迭代时,SceneKit 会自动提交事务,自动将事务期间所做的所有更改应用到演示场景图(即当前正在显示的场景图的版本)。

这篇博文 [31700071] 深入探讨了您可以/应该在主线程中做什么以及它如何影响渲染循环的问题。

【讨论】:

这将有竞争条件,因为 rendererhandleTap 在不同的线程上运行 我总是有可能做错了......但我已经在几场比赛中使用了这种结构。我建立了一个模拟器来玩游戏,并且不间断地运行测试长达整整一周。我还跟踪了实时崩溃(无)和 FPS 统计数据。我使用 DispatchQueue.main.async (打开计时器),以便计时器在主线程中运行。 GameControl 执行所有节点操作。 这不是线程安全的。它对竞争条件开放,甚至可能崩溃。 这并不能阻止比赛......除非您在主线程中使用闭包 API(类似于 update(_ oldPos: CGPoint))进行更新,但事实并非如此。否则直接阅读肯定会造成竞态 好的,对不起,我帮不了你。祝你好运。

以上是关于如何在 SceneKit 的渲染队列中处理触摸事件的主要内容,如果未能解决你的问题,请参考以下文章

iOS触摸事件处理详解

响应者链条,如何获取最佳的点击view 以及内部实现

iOS学习笔记05-触摸事件

事件的产生,传递以及响应链

ios开发事件处理之 :二:事件的产生与传递

iOS 在处理触摸事件之前执行操作