swift - 我正在尝试检测 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 接触或碰撞时,什么也没有发生。
解决方案
要在两个对象之间实现碰撞检测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
}
}
推荐阅读
- imagemagick - 如何使用 imagemagick 删除图像的一部分
- wpf - 按钮 BorderBrush 不会改变
- java - Alternating Output Number and Letter in 2 Threads without Lock
- c# - 如何在 c# 中将 json 转换为没有任何模型或映射器的对象
- json - 使用 mongoose 查询:如何分配 Mongo _id?
- domain-driven-design - 日期传输对象(DTO)和领域驱动设计模式的表示对象有什么区别?
- java - CallableStatement executeQuery 返回 resultSet 但执行返回 false
- python - Python 中 mongoDB 忽略异常的高级实践
- javascript - 根据视频播放/暂停触发 css 更改
- javascript - 如何在没有 jQuery 或 Bootstrap 的情况下为切换的导航栏设置动画?