首页 > 解决方案 > 我正在尝试检测 ARKit SceneKit 中两个节点之间的联系

问题描述

我正在创建一个 AR UFO 入侵者游戏。我用搅拌机创建了一个不明飞行物,并将 .dae 文件添加到 xCode 下的 art.scnassets 并将其转换为 .scn。接下来,我使用“touches begin”功能创建了一个橙色激光节点。我正在尝试检测 UFO 节点和橙色激光节点何时接触,从而导致得分增加一分。该应用程序在 UFO 节点移动和橙色激光节点在触摸时创建并运行时构建和运行,但是,它不会检测到接触或碰撞并添加到分数中。

我已经尝试过枚举 BodyType 和 struct OptionSet。

import UIKit
import SceneKit
import ARKit
import Foundation

enum BodyType:Int {

    case UFO = 1
    case orangelaser = 2
    case redlaser = 4

}

extension UIColor {
    convenience init(rgb: UInt) {
        self.init(
            red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgb & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
}


class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate, SCNPhysicsContactDelegate {

// .........

    var gamerunning: Int = 0

    var score: Int = 0
    var lives: Int = 3
    var ammo: Int = 100

    var coinCount = 0
    var displaylivesammocounter = 0

    var rotationtimer: Int = 0
    var UFOxposition: Float = 0
    var UFOyposition: Float = 0
    var UFOzposition: Float = 0

    var UFONode: SCNNode!
    var orangelaserNode: SCNNode!

    var detectedPlanes: [String : SCNNode] = [:]
    var translation = matrix_identity_float4x4
    var gamePos = SCNVector3Make(0.0, 0.0, 0.0)

   // ......

    override func viewDidLoad() {
        super.viewDidLoad()

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



        // Show statistics such as fps and timing information
        sceneView.showsStatistics = true

        // Create a new scene
        let scene = SCNScene(named: "art.scnassets/UFO final with paint.scn")!

        // Set the scene to the view
        sceneView.scene = scene
        sceneView.scene.physicsWorld.contactDelegate = self

        createUFO()

        // .............

        UFONode.isHidden = true

        // .........


    func createUFO() {


        UFONode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
        UFONode.physicsBody?.restitution = 0.0
        UFONode.physicsBody?.friction = 1.0

        // Set the category masks
        UFONode.physicsBody?.categoryBitMask = BodyType.UFO.rawValue

        // Set the contact masks
        UFONode.physicsBody?.contactTestBitMask = BodyType.orangelaser.rawValue

        // Set the collission masks
        UFONode.physicsBody?.collisionBitMask = BodyType.orangelaser.rawValue

        UFONode = sceneView.scene.rootNode.childNode(withName: "UFO", recursively: true)!
        sceneView.scene.rootNode.addChildNode(UFONode)

    }

    // .............

    func startGame() {

        // .................

        gamerunning = 1

        UFONode.isHidden = false

        // ...................

        let UFOrotationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in

            if (self.rotationtimer >= 1) {
                timer.invalidate()
            }

            self.UFONode.runAction(SCNAction.repeat(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1), count: 1))

            func random(min: CGFloat, max: CGFloat) -> CGFloat {
                assert(min < max)
                return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
            }

            self.UFOyposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOxposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOzposition = Float(random(
                min: -0.2,
                max: 0.2
           ))

            self.UFONode.runAction(SCNAction.moveBy(x: CGFloat(self.UFOxposition), y: CGFloat(self.UFOyposition), z: CGFloat(self.UFOzposition), duration: 1.0))

        })

    }

    func restartSameGame(){

        // ..............

        gamerunning = 1

        UFONode.isHidden = false

        // ................

        let UFOrotationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in

            if (self.rotationtimer >= 1) {
                timer.invalidate()
            }

            self.UFONode.runAction(SCNAction.repeat(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1), count: 1))

            func random(min: CGFloat, max: CGFloat) -> CGFloat {
                assert(min < max)
                return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
            }

            self.UFOyposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOxposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOzposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFONode.runAction(SCNAction.moveBy(x: CGFloat(self.UFOxposition), y: CGFloat(self.UFOyposition), z: CGFloat(self.UFOzposition), duration: 1.0))

        })

    }

    func newGame() {

        // ................

        score = 0
        ammo = 100
        lives = 3

        // ......................

        gamerunning = 1

        UFONode.isHidden = false

        // ........................

        let UFOrotationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in

            if (self.rotationtimer >= 1) {
                timer.invalidate()
            }

            self.UFONode.runAction(SCNAction.repeat(SCNAction.rotateBy(x: 0, y: 1, z: 0, duration: 1), count: 1))

            func random(min: CGFloat, max: CGFloat) -> CGFloat {
                assert(min < max)
                return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
            }

            self.UFOyposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOxposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFOzposition = Float(random(
                min: -0.2,
                max: 0.2
            ))

            self.UFONode.runAction(SCNAction.moveBy(x: CGFloat(self.UFOxposition), y: CGFloat(self.UFOyposition), z: CGFloat(self.UFOzposition), duration: 1.0))

        })

    }

    @IBAction func PlayGameButton(_ sender: Any) {

        startGame()

    }

    @IBAction func PlayAgainButton(_ sender: Any) {

            if interstitial.isReady {
                interstitial.present(fromRootViewController: self)
            } else {

                self.rotationtimer = 0

                if (self.ammo > 0 && self.lives > 0)  {
                    self.restartSameGame()
                } else {
                    self.newGame()
                }

        }

    }

    func addScore() {

        score = score + 1

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        if (lives <= 0) {
            gameOver()
        }

        if (ammo <= 0) {
            gameOver()
        }

    }

    func decreaseLives() {

        if (lives > 0) {
            lives = lives - 1
        }

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        if (lives <= 0) {
            gameOver()
        }

        if (ammo <= 0) {
            gameOver()
        }

    }

    func decreaseAmmo() {

        if (ammo > 0) {
            ammo = ammo - 1
        }

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        if (lives <= 0) {
            gameOver()
        }

        if (ammo <= 0) {
            gameOver()
        }

    }

    // .....................

    func gameOver() {

        DisplayScore.text = "\(score)"
        DisplayLives.text = "\(lives)"
        DisplayAmmo.text = "\(ammo)"

        UFONode.isHidden = true
        gamerunning = 0


        MoreLivesAmmoButton.isHidden = false
        EarnRewardsButton.isHidden = false
        PlayAgainButton.isHidden = false

        rotationtimer = 1

        // .....................

        GameOverLabel.isHidden = false



    }

    // .............................

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // Create a session configuration
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = [.horizontal, .vertical]

        // Run the view's session
        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        // Pause the view's session
        sceneView.session.pause()
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let frame = sceneView.session.currentFrame else { return } //1
        let camMatrix = SCNMatrix4(frame.camera.transform)
        let direction = SCNVector3Make(-camMatrix.m31 * 5.0, -camMatrix.m32 * 10.0, -camMatrix.m33 * 5.0) //2
        let position = SCNVector3Make(camMatrix.m41, camMatrix.m42, camMatrix.m43) //3
        if ((ammo > 0) && (lives > 0) && (UFONode.isHidden == false) && (gamerunning == 1)) {
            let orangelaser = SCNCapsule(capRadius: 0.25, height: 1.0) //1
            orangelaser.firstMaterial?.diffuse.contents = UIColor(red: 245.0 / 255.0, green: 127.0 / 255.0, blue: 44.0 / 255.0, alpha: 1 )
            orangelaser.firstMaterial?.emission.contents = UIColor(red: 245.0 / 255.0, green: 127.0 / 255.0, blue: 44.0 / 255.0, alpha: 1 ) //2
            orangelaserNode = SCNNode(geometry: orangelaser)
            orangelaserNode.scale = SCNVector3(x: 0.009, y: 0.009, z: 0.009)
            orangelaserNode.position = position //3
            self.orangelaserNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
            orangelaserNode.physicsBody?.mass = 1.5; // 1.5kg
            orangelaserNode.physicsBody?.restitution = 0.25
            orangelaserNode.physicsBody?.friction = 0.75
            orangelaserNode.physicsBody?.categoryBitMask = BodyType.orangelaser.rawValue
            orangelaserNode.physicsBody?.contactTestBitMask = BodyType.UFO.rawValue
            orangelaserNode.physicsBody?.collisionBitMask = BodyType.UFO.rawValue
            sceneView.scene.rootNode.addChildNode(orangelaserNode)
            orangelaserNode.runAction(SCNAction.sequence([SCNAction.wait(duration: 10.0), SCNAction.removeFromParentNode()])) //5
            orangelaserNode.physicsBody?.applyForce(direction, asImpulse: true) //6

            decreaseAmmo()

        }
    }

    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        print("contact happened!")
        if (contact.nodeA.physicsBody?.categoryBitMask == BodyType.UFO.rawValue && contact.nodeB.physicsBody?.categoryBitMask == BodyType.orangelaser.rawValue ){

            print("collision UFO and orangelaser")

            addScore()

        } else if (contact.nodeB.physicsBody?.categoryBitMask == BodyType.UFO.rawValue && contact.nodeA.physicsBody?.categoryBitMask == BodyType.orangelaser.rawValue ){

            print("collision orangelaser and UFO")

            addScore()

        }

    }




    // MARK: - ARSCNViewDelegate

/*
    // Override to create and configure nodes for anchors added to the view's session.
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        let node = SCNNode()

        return node
    }
*/

    func session(_ session: ARSession, didFailWithError error: Error) {
        // Present an error message to the user

    }

    func sessionWasInterrupted(_ session: ARSession) {
        // Inform the user that the session has been interrupted, for example, by presenting an overlay

    }

    func sessionInterruptionEnded(_ session: ARSession) {
        // Reset tracking and/or remove existing anchors if consistent tracking is required

    }
}

应用程序构建并运行。我没有看到任何错误消息。然而,当橙色激光节点与 UFO 接触或碰撞时,什么也没有发生。

标签: swiftxcodescenekitarkit

解决方案


要在两个对象之间实现碰撞检测ARKit,请确保您已遵循每个步骤:

1.创建一个包含所有主体类型的枚举:

enum BodyType : Int {
    case type1 = 1
    case type2 = 2
} 

2.应用物理体categoryBitMask所有需要碰撞的节点:

node1.physicsBody = SCNPhysicsBody(type: .static, shape: nil)
node1.physicsBody?.categoryBitMask = BodyType.type1.rawValue

node2.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
node2.physicsBody?.categoryBitMask = BodyType.type2.rawValue

3.添加contactTestBitMask到您的移动对象以过滤仅在您的节点上的碰撞触发器:

node2.physicsBody?.contactTestBitMask = BodyType.type2.rawValue

4.符合SCNPhysicsContactDelegate并执行physicsWorld(_: didBegin:)

将此添加到viewDidLoad()

self.sceneView.scene.physicsWorld.contactDelegate = self

按照这些步骤,只要您的节点之间发生冲突,physicsWorld(_: didBegin:)就会调用委托:

extension ViewController: SCNPhysicsContactDelegate {
    func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
        // Collision happened between contact.nodeA and contact.nodeB
    }
}


推荐阅读