需要在 xcode 上为我的项目/用户与增强对象交互的方式实现虚拟按钮

need to implement virtual buttons on xcode for my project/ way for users to interact with augmented objects

我正在做我的增强现实研究项目,我想让用户触摸我的 true/false 按钮,如下图所示,在相机视图中而不是触摸屏中。有什么办法可以做到吗?

如果你想制作虚拟按钮,你有几种选择:

1:(实验性): 您可以渲染一个带有 2 个按钮的自定义 UIView

2nd: 创建两个带有 SCNPlane GeometrySCNNodes,然后使用图像作为正确或错误的提示。

3rd: 使用上图中显示的 SCNText

如果您选择了第一个选项,则无需执行 SCNHitTest,因为您可以使用 IBActions 来确定点击了哪一个。

对于其他选项,您需要使用 SCNHitTest 其中:

looks for SCNGeometry objects along the ray you specify. For each intersection between the ray and and a geometry, SceneKit creates a hit-test result to provide information about both the SCNNode object containing the geometry and the location of the intersection on the geometry’s surface.

我不会与您详细讨论第一个选项,因为这不是 'standard' 也不是广泛采用的做法(如果全部)。

让我们先看看使用两个 SCNNodes 作为 'virtual buttons' 和一个 SCNPlaneGeometry:

/// Creates A Menu With A True Or False Button Using SCNPlane Geometry
func createTrueOrFalseMenu(){

    //1. Create A Menu Holder
    let menu = SCNNode()

    //2. Create A True Button With An SCNPlane Geometry & Green Colour
    let trueButton = SCNNode();
    let trueButtonGeometry = SCNPlane(width: 0.2, height: 0.2)
    let greenMaterial = SCNMaterial()
    greenMaterial.diffuse.contents = UIColor.green
    greenMaterial.isDoubleSided = true
    trueButtonGeometry.firstMaterial = greenMaterial
    trueButton.geometry = trueButtonGeometry
    trueButton.name = "True"

    //3. Create A False Button With An SCNPlane Geometry & A Red Colour
    let falseButton = SCNNode();
    let falseButtonGeometry = SCNPlane(width: 0.2, height: 0.2)
    let redMaterial = SCNMaterial()
    redMaterial.diffuse.contents = UIColor.red
    redMaterial.isDoubleSided = true
    falseButtonGeometry.firstMaterial = redMaterial
    falseButton.geometry = falseButtonGeometry
    falseButton.name = "False"

    //4. Set The Buttons Postions
    trueButton.position = SCNVector3(-0.2,0,0)
    falseButton.position = SCNVector3(0.2,0,0)

    //5. Add The Buttons To The Menu Node & Set Its Position
    menu.addChildNode(trueButton)
    menu.addChildNode(falseButton)
    menu.position = SCNVector3(0,0, -1.5)

    //6. Add The Menu To The View
    augmentedRealityView.scene.rootNode.addChildNode(menu)

}

现在让我们看看使用两个 SCNNodes 作为 'virtual buttons' 使用一个 SCNTextGeometry:

/// Creates A Menu With A True Or False Button Using SCNText Geometry
func createTrueOrFalseMenuWithText(){

    //1. Create A Menu Holder
    let menu = SCNNode()

    //2. Create A True Button With An SCNText Geometry & Green Colour
    let trueButton = SCNNode();
    let trueTextGeometry = SCNText(string: "True" , extrusionDepth: 1)
    trueTextGeometry.font = UIFont(name: "Helvatica", size: 3)
    trueTextGeometry.flatness = 0
    trueTextGeometry.firstMaterial?.diffuse.contents = UIColor.green
    trueButton.geometry = trueTextGeometry
    trueButton.scale = SCNVector3(0.01, 0.01 , 0.01)
    trueButton.name = "True"

    //3. Create A False Button With An SCNText Geometry & Red Colour
    let falseButton = SCNNode();
    let falseTextGeometry = SCNText(string: "False" , extrusionDepth: 1)
    falseTextGeometry.font = UIFont(name: "Helvatica", size: 3)
    falseTextGeometry.flatness = 0
    falseTextGeometry.firstMaterial?.diffuse.contents = UIColor.red
    falseButton.geometry = falseTextGeometry
    falseButton.scale = SCNVector3(0.01, 0.01 , 0.01)
    falseButton.name = "False"

    //4. Set The Buttons Postions
    trueButton.position = SCNVector3(-0.2,0,0)
    falseButton.position = SCNVector3(0.2,0,0)

    //5. Add The Buttons To The Menu Node & Set Its Position
    menu.addChildNode(trueButton)
    menu.addChildNode(falseButton)
    menu.position = SCNVector3(0,0, -1.5)

    //6. Add The Menu To The View
    augmentedRealityView.scene.rootNode.addChildNode(menu)
}

现在我们有了不同的实现设置,然后您需要创建某种逻辑来处理我们是否触摸了 true 或 false 按钮​​。

您会注意到,在创建 true 或 false 按钮​​时,我使用了它们的 name property 这将帮助我们确定点击了哪个按钮,例如:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

//1. Get The Current Touch Location
guard let touchLocation = touches.first?.location(in: self.augmentedRealityView),

//2. Perform An SCNHitTest & Get The Node Touched
let hitTestNode = self.augmentedRealityView.hitTest(touchLocation, options: nil).first?.node else { return }

//3. Determine Whether The User Pressed True Or False & Handle Game Logic
if hitTestNode.name == "True"{

    print("User Has A Correct Answer")

}else if hitTestNode.name == "False"{

    print("User Has An InCorrect Answer")

   }
 }

更新: 为了检测在标准触摸之外选择了哪个虚拟按钮,您有两个选项,一个确定按钮是 inViewOfFrostum 还是使用基于指定 CGPoint 的 SCNHitTest,例如屏幕中心

查看第一个选项,我们需要考虑到 ARCamera 有一个 Frostrum,其中显示了我们的内容:

然后您可以通过创建一个函数来确定用户是否选择了任一 virtualButton。但是,这可能不是您想要的,因为这意味着您必须确保 SCNNode 按钮的位置足够分开,以确保一次只能看到一个按钮。

如果您需要此选项,您首先需要两个 SCNNodes 例如:

  var trueButton: SCNNode!
  var falseButton: SCNNode!

然后像这样创建一个函数:

/// Detects If An Object Is In View Of The Camera Frostrum
func detectButtonInFrostrumOfCamera(){

    //1. Get The Current Point Of View
    if let currentPointOfView = augmentedRealityView.pointOfView{

        if augmentedRealityView.isNode(trueButton, insideFrustumOf: currentPointOfView){

            print("True Button Is In View & Has Been Selected As The Answer")

        }

        if augmentedRealityView.isNode(falseButton, insideFrustumOf: currentPointOfView){

            print("False Button Is In View & Has Been Selected As The Answer")

        }
    }
}

然后您将在以下 delegate 回调中触发,例如

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

    detectButtonInFrostrumOfCamera()

}

然而,更可能的解决方案是使用 CGPoint 作为参考执行虚拟光线投射。

在这个例子中,让我们首先创建我们的 CGPoint var,它将指向屏幕的中心:

var screenCenter: CGPoint!

然后我们将在 viewDidLoad 中这样设置:

 DispatchQueue.main.async {
            self.screenCenter = CGPoint(x: self.view.bounds.width/2, y: self.view.bounds.height/2)

 }

然后我们将创建一个函数,它对屏幕中心执行 SCNHitTest 以查看这些点是否触及 true 或 false 按钮​​,例如:

/// Detects If We Have Intersected A Virtual Button
func detectIntersetionOfButton(){

    guard let rayCastTarget = self.augmentedRealityView?.hitTest(screenCenter, options: nil).first else { return }

    if rayCastTarget.node.name == "True"{

       print("User Has Selected A True Answer")

    }

    if rayCastTarget.node.name == "False"{

        print("User Has Selected A False Answer")

    }

}

将在以下委托回调中再次调用:

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

   detectIntersetionOfButton()
}

@Josh Robbins - 首先,我要感谢您的好意以及您为回答问题所花费的时间和精力。

我赞扬了这个有趣的信息性答案,它可以适用于许多不同的用途和方式,而且解释得很好(一如既往地与你的答案:-)