简体   繁体   English

Spritekit和Scenekit的HitTest

[英]HitTest for both Spritekit and Scenekit

I have what seems to be a pretty basic ViewController with a SCNView which has an overlaySKScene . 我有什么似乎是一个非常基本的ViewController具有SCNView它有一个overlaySKScene

The issue I have is that I don't want the tap to be detected in the underlying self.scene ( gameScene in the example below) if it was first detected in a SpriteKit node in the overlay scene. 我遇到的问题是,如果首先在覆盖场景中的SpriteKit节点中检测到它,我希望在底层的self.scene (下gameScene中的gameScene )中检测到tap。

With the following, both scenes report that a hit occurred even though the tap happened on the SKOverlay scene node actionButtonNode . 使用以下内容,即使在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 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")
    }
  }
}

I don't have a fancy solution with code for you but two possible work arounds: 我没有为您提供代码的奇特解决方案,但有两种可能的解决方法:

a. 一个。 avoid the situation by handling all the touches in the main viewcontroller. 通过处理主viewcontroller中的所有触摸来避免这种情况。 I do that with the SceneKit and SpriteKit overlay too, keep the logic out of the overlay scene and use it only for what it is intended (a graphic overlay, not a regular interactive SpriteKit scene). 我也使用SceneKit和SpriteKit叠加来实现这一点,将逻辑保留在叠加场景之外,并仅将其用于预期目标(图形叠加,而不是常规的交互式SpriteKit场景)。 You can test if the actionbutton, or any other, was hit in the Scenekit viewcontroller. 您可以测试是否在Scenekit视图控制器中命中了动作按钮或任何其他按钮。 If no button was hit the 3D scene was hit. 如果没有按下按钮,则3D场景被击中。

b. Add a bool to the main Scenekit view controller, isHandledInOverlay and set it in the SpriteKit scene if the button was touched. 将bool添加到主要的Scenekit视图控制器isHandledInOverlay,并在触摸按钮时将其设置在SpriteKit场景中。 In the main Scenekit view controller check the bool in handletap and return and do nothing if true. 在主要的Scenekit视图控制器中检查handletap中的bool并返回,如果为true则不执行任何操作。

Edit based on comments: I am simply suggesting to move this piece of code: 根据评论进行编辑:我只是建议移动这段代码:

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

to the handleTap function in ViewController. 到ViewController中的handleTap函数。 You will have to make a var that holds the SKOverlayScene, which in turn has the actionButtonNode (and any other buttons) as a property, so in handleTap you end up with something like: 你必须创建一个包含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)
        }
    }
}

So first you compare the 2D tap location to the 2D coordinates of the button(s) to see if they got tapped, and if not then you use the 2D tap location to see if an object in the 3D scene was tapped using the hit test. 因此,首先将2D点击位置与按钮的2D坐标进行比较以查看它们是否被点击,如果没有,则使用2D点击位置来查看是否使用点击测试点击了3D场景中的对象。

Edit: was just looking at my own (obj c) code and noticed I flip the Y coordinate in the main viewcontroller to get the coordinates in the SKOverlay: 编辑:只是查看我自己的(obj c)代码并注意到我在主视图控制器中翻转Y坐标以获取SKOverlay中的坐标:

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

You may have to do that with the location you feed to the contains function of the button nodes. 您可能必须使用您提供给按钮节点的contains函数的位置来执行此操作。

Hesitated to post because I'm not an expert and if I'm understanding Xartec correctly, I "think" I solved it in a similar way. 犹豫要发帖,因为我不是专家,如果我正确理解Xartec,我“想”我以类似的方式解决了它。 I also needed screen edge pan in scenekit - took a while to figure that one out. 我还需要在scenekit中使用屏幕边缘平移 - 花了一些时间来计算出那个。

I created handleTap and handlePan (with recognizer states of begin, changed, and end). 我创建了handleTap和handlePan(识别器状态为begin,changed和end)。

I make a call to CheckMenuTap that cycles through all of my active SpriteKit buttons and returns true if one of the buttons was tapped. 我打电话给CheckMenuTap循环浏览所有活动的SpriteKit按钮,如果其中一个按钮被点击则返回true。

If NOT true, then I process the hitTest and see if I get a node hit. 如果不是,那么我处理hitTest并查看是否有节点命中。 That was the only way I found to avoid getting a hit on both SpriteKit and SceneKit if I don't want them - hope that helps. 这是我发现如果我不想要它们就避免在SpriteKit和SceneKit上受到打击的唯一方法 - 希望有所帮助。

@IBAction func handleTap(recognizer: UITapGestureRecognizer) { let location: CGPoint = recognizer.location(in: scnView) @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
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM