Spritekit和Scenekit的HitTest

时间:2018-02-03 19:44:29

标签: ios swift scenekit

我有一个非常基本的ViewController SCNView,其overlaySKScene

我遇到的问题是,如果首次检测到,我希望在基础self.scene(下例中的gameScene)中检测到点按在叠加场景中的SpriteKit节点中。

使用以下内容,两个场景都会报告即使在SKOverlay场景节点actionButtonNode上发生了点击也发生了点击。

的ViewController

import UIKit
import SceneKit

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    sceneView = self.view as? SCNView
    gameScene = GameScene()

    let view = sceneView
    view!.scene = gameScene
    view!.delegate = gameScene as? SCNSceneRendererDelegate
    view!.overlaySKScene = OverlayScene(size: self.view.frame.size)

    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTap(_:)))
    tapGesture.cancelsTouchesInView = false
    view!.addGestureRecognizer(tapGesture)
  }

  @objc func handleTap(_ sender:UITapGestureRecognizer) {
    let projectedOrigin = self.sceneView!.projectPoint(SCNVector3Zero)
    let taplocation = sender.location(in: self.view!)
    let opts = [ SCNHitTestOption.searchMode:1, SCNHitTestOption.ignoreHiddenNodes:0 ]
    let hitList = self.sceneView!.hitTest(taplocation, options: opts)
    if hitList.count > 0 {
      for hit in hitList {
        print("hit:", hit)
      }
    }
  }
}

OverlayScene

import UIKit
import SpriteKit

class OverlayScene: SKScene {
  var actionButtonNode: SKSpriteNode!

  override init(size: CGSize) {
    super.init(size: size)

    self.backgroundColor = UIColor.clear

    self.actionButtonNode = SKSpriteNode()
    self.actionButtonNode.size = CGSize(width: size.width / 2, height: 60)
    self.actionButtonNode.position = CGPoint(x: size.width / 2, y: 80)
    self.actionButtonNode.color = UIColor.blue
    self.addChild(self.actionButtonNode)
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let location = touch?.location(in: self)
    if self.actionButtonNode.contains(location!) {
      print("actionButtonNode touch")
    }
  }
}

2 个答案:

答案 0 :(得分:1)

我没有为您提供代码的奇特解决方案,但有两种可能的解决方法:

一个。通过处理主viewcontroller中的所有触摸来避免这种情况。我也使用SceneKit和SpriteKit叠加来实现这一点,将逻辑保留在叠加场景之外,并仅将其用于预期目标(图形叠加,而不是常规的交互式SpriteKit场景)。您可以测试是否在Scenekit视图控制器中命中了动作按钮或任何其他按钮。如果没有按下按钮,则3D场景被击中。

湾将bool添加到主要的Scenekit视图控制器isHandledInOverlay,并在触摸按钮时将其设置在SpriteKit场景中。在主要的Scenekit视图控制器中检查handletap中的bool并返回,如果为true则不执行任何操作。

根据评论进行修改: 我只是建议移动这段代码:

if self.actionButtonNode.contains(location!) {
  print("actionButtonNode touch")
}

到ViewController中的handleTap函数。你必须创建一个包含SKOverlayScene的var,而后者又将actionButtonNode(以及任何其他按钮)作为属性,因此在handleTap中你会得到类似的结果:

if self.theOverlay.actionButtonNode.contains(taplocation) {
  print("actionButtonNode touch")
} else if self.theOverlay.action2ButtonNode.contains(taplocation) {
  print("action2ButtonNode touch")
} else if self.theOverlay.action3ButtonNode.contains(taplocation) {
  print("action3ButtonNode touch")
} else {
    //no buttons hit in 2D overlay, let's do 3D hittest:
    let opts = [ SCNHitTestOption.searchMode:1, SCNHitTestOption.ignoreHiddenNodes:0 ]
    let hitList = self.sceneView!.hitTest(taplocation, options: opts)
    if hitList.count > 0 {
        for hit in hitList {
           print("hit:", hit)
        }
    }
}

首先,您将2D点按位置与按钮的2D坐标进行比较以查看是否点击了按钮,如果没有,则使用2D点击位置查看3D场景中的对象是否被点击了热门考试。

编辑:只是查看我自己的(obj c)代码并注意到我在主视图控制器中翻转Y坐标以获取SKOverlay中的坐标:

CGPoint tappedSKLoc = CGPointMake(location.x, self.menuOverlayView.view.frame.size.height-location.y);

您可能必须使用您提供给按钮节点的contains函数的位置来执行此操作。

答案 1 :(得分:1)

犹豫要发帖,因为我不是专家,如果我正确理解Xartec,我认为&#34;我以类似的方式解决了它。我还需要在scenekit中使用屏幕边缘平移 - 花了一些时间来计算出那个。

我创建了handleTap和handlePan(识别器状态为begin,changed和end)。

我打电话给CheckMenuTap,循环浏览所有活动的SpriteKit按钮,如果其中一个按钮被点击,则返回true。

如果不是,那么我处理hitTest并查看是否有节点命中。如果我不想要它们,那么这是我发现避免在SpriteKit和SceneKit上受到打击的唯一方法 - 希望有所帮助。

@IBAction func handleTap(识别器:UITapGestureRecognizer)     {         let location:CGPoint = recognizer.location(in:scnView)

    if(windowController.checkMenuTap(vLocation: location) == true)
    {
        return
    }

    let hitResults: [SCNHitTestResult]  = scnView.hitTest(location, options: hitTestOptions)
    if(data.gameStateManager.gameState == .run)
    {
        for vHit in hitResults
        {
            //print("Hit: \(vHit.node.name)")
            if(vHit.node.name?.prefix(5) == "Panel")
            {
                if(data.gamePaused == true) { return }
                windowController.gameControl.selectPanel(vPanel: vHit.node.name!)
                return
            }
        }
    }
}