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

时间:2018-04-18 02:18:51

标签: augmented-reality arkit user-interaction

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

enter image description here

3 个答案:

答案 0 :(得分:1)

如果您想制作虚拟按钮,您有多种选择:

1 :(实验性):您可以在其上呈现带有2个按钮的自定义UIView

第二名:使用SCNNodes创建两个SCNPlane Geometry,然后使用图片提供真或假提示。

第3名:使用上图中显示的SCNText

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

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

  

沿您指定的光线查找SCNGeometry对象。对于每一个   在射线和几何体之间的交集,SceneKit创建了一个   命中测试结果,提供有关SCNNode对象的信息   包含几何和交叉点的位置   几何的表面。

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

让我们先看看使用两个SCNNodes作为带有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将两个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按钮。

您会注意到,在创建真或假按钮时,我使用了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还是使用基于的SCNHitTest指定的CGPoint例如屏幕的中心

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

enter image description here

然后,您可以通过创建确定此功能的函数来确定用户是否选择了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的功能,以查看这些点是否触及真或假按钮,例如:

/// 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()
}

答案 1 :(得分:0)

@Josh罗宾斯 - 首先,我要感谢你的善意,以及你花在时间和精力上的答案。

我认为这个有趣的信息化答案可以适用于许多不同的用途和方法,并且也得到了很好的解释(一如既往地提供答案: - )

答案 2 :(得分:0)

嘿@JoshRobbins我正在使用你给定的代码用SCNTextgeometry创建两个SCNNode,然后使用光线投射代码。我无法在设备上获得任何输出。当我运行项目时,真正的错误按钮不可见。我写的代码有什么问题吗?任何帮助将不胜感激 class ViewController:UIViewController,ARSCNViewDelegate {

@IBOutlet var sceneView: ARSCNView!

var screenCenter: CGPoint!
var trueButton: SCNNode!
var falseButton: SCNNode!

override func viewDidLoad() {
    super.viewDidLoad()

    // Set the view's delegate
    sceneView.delegate = self

    /// 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
        sceneView.scene.rootNode.addChildNode(menu)
    }

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

    }


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

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

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

            print("User Has Selected A True Answer")

        }

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

            print("User Has Selected A False Answer")

        }
    }

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

        detectIntersetionOfButton()
    }
}