I am creating an AR UFO invaders game. I have created a UFO with blender and added the .dae file to xCode under art.scnassets and converted it to .scn. Next, I created an orange laser node using the "touches begin" function. I am attempting to detect when the UFO node and the orange laser node come into contact resulting in adding one point to the score. The app builds and runs with the UFO node moving and the orange laser node being created upon touch as well, however, it does not detect a contact or collision and add to the score.
I have tried both enum BodyType and 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
}
}
App builds and runs. I do not see any error messages. However when orange laser nodes come into contact with or collide with the UFO nothing happens.
physicsBody
. In order to do this, setshowPhysicsShapes
or something similar totrue
. If there are physicsBody, you should see a line surrounding your node. If there's no physicsBody, you have to move this lineUFONode = sceneView.scene.rootNode.childNode(withName: "UFO", recursively: true)! sceneView.scene.rootNode.addChildNode(UFONode)
above thephysicsBody
creation – Andrew21111