ios - 如何动态跟踪场景中的 SCNNode 以进行删除?
问题描述
必填:第一次写app,相关代码如下。我的代码主要按照我的意愿运行,但没有达到我的动态对象跟踪目标。
我正在使用 Swift 和 Scenekit 构建一个简单的益智游戏,类似于 3d 版的糖果迷恋。
我有class Cube
一个extends SCNNode
。在初始化时,这个类将随机绘制一个 5x5 的立方体,SCNBoxes
每个盒子是红色、绿色或蓝色(一个盒子的所有 6 个边都是一种颜色)。
游戏的目标是通过移除类似颜色的 SCNBox 的“链”来获得最高分。当链条被移除时,立方体应该识别重力并下降以填充由移除的链条产生的空隙。这是我需要动态跟踪位置的地方。随着立方体落入缝隙中,它们的邻居发生了变化。
我的方法:构建一个struct CubeDetails
具有属性var color: String
和var location: SCNVector3
. 接下来,构建一个masterCubeDict = [SCNNode: CubeDetails]
包含 1 种颜色的所有立方体的字典(颜色由 hittestresult 提供)。
每次用户点击一个立方体时,抓取它的颜色,刷新 masterCubeDict,然后在 SCNVector3 位置上使用数学来确定哪些立方体是邻居。
我认为我在 scnvector3 上使用数学查找“立方体邻居”的算法是我的出发点。场景套件节点必须有更好的方法来识别/找到彼此,对吧?
另外——我希望立方体的物理特性让它们掉落并且完全没有反弹/滑动。他们应该只直接向上/向下移动。碰撞永远不应该发生。我以为我通过立方体的摩擦、恢复和质量正确地实现了这一点,但我没有得到我想要的结果。
class Cube
import SceneKit
class Cube : SCNNode {
let cubeWidth:Float = 0.95
let spaceBetweenCubes:Float = 0.05
var cubecolor:UIColor = UIColor.black
var masterCubeDict: [SCNNode: CubeDetails] = [:]
struct CubeDetails {
var color:String
var position:SCNVector3
}
override init() {
super.init()
let cubeOffsetDistance = self.cubeOffsetDistance()
var cubeColorString: String = ""
var xPos:Float = -cubeOffsetDistance
var yPos:Float = -cubeOffsetDistance
var zPos:Float = -cubeOffsetDistance
let xFloor:Float = -1.5
let yFloor:Float = -1.5
let zFloor:Float = -1.5
let floorGeo = SCNBox(width: 20, height: 0, length: 20, chamferRadius: 0)
let floor = SCNNode(geometry: floorGeo)
floor.position = SCNVector3(x: xFloor, y: yFloor, z: zFloor)
floor.name = "floor"
floor.opacity = 0.0
floor.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil)
floor.physicsBody?.collisionBitMask = 1
floor.physicsBody?.friction = 1.0
self.addChildNode(floor)
for _ in 0..<5 {
for _ in 0..<5 {
for _ in 0..<5 {
let cubeGeometry = SCNBox(width: CGFloat(cubeWidth), height: CGFloat(cubeWidth), length: CGFloat(cubeWidth), chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = randomColor()
//unwrap material (type any) and cast to uicolor for switch
if let unwrapColor: UIColor = material.diffuse.contents as? UIColor {
switch unwrapColor {
case UIColor.red:
cubeColorString = "red"
case UIColor.green:
cubeColorString = "green"
case UIColor.blue:
cubeColorString = "blue"
default:
cubeColorString = "black"
}
} else { print("Error unwrapping color") }
cubeGeometry.materials = [material, material, material, material, material, material]
let cube = SCNNode(geometry: cubeGeometry)
cube.name = cubeColorString
cube.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)
cube.physicsBody?.restitution = 0.0
cube.physicsBody?.isAffectedByGravity = true
cube.physicsBody?.mass = 25.0
cube.physicsBody?.friction = 1.0
cube.physicsBody?.collisionBitMask = 1
cube.position = SCNVector3(x: xPos, y: yPos, z: zPos)
let details = CubeDetails(color: cubeColorString, position: cube.position)
//add cube details to the master dict
masterCubeDict[cube] = details
//print(masterCubeDict)
xPos += cubeWidth + spaceBetweenCubes
self.addChildNode(cube)
}
xPos = -cubeOffsetDistance
yPos += cubeWidth + spaceBetweenCubes
}
xPos = -cubeOffsetDistance
yPos = -cubeOffsetDistance
zPos += cubeWidth + spaceBetweenCubes
}
}
private func cubeOffsetDistance()->Float {
return (cubeWidth + spaceBetweenCubes) / 2
}
private func randomColor() -> UIColor{
var tmpColor: UIColor
let num = Int.random(in:0...2)
switch num {
case 0:
tmpColor = UIColor.red
case 1:
tmpColor = UIColor.blue
case 2:
tmpColor = UIColor.green
default:
tmpColor = UIColor.black
}
return tmpColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
GameViewController
import UIKit
import QuartzCore
import SceneKit
var myMasterCubeDict: [SCNNode: Cube.CubeDetails] = [:]
class GameViewController: UIViewController {
let gameCube = Cube()
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
// let scene = SCNScene(named: "art.scnassets/ship.scn")!
let scene = SCNScene()
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 2, y: 0, z: 20)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// init cube
myMasterCubeDict = gameCube.masterCubeDict
scene.rootNode.addChildNode(gameCube)
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
@objc
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result = hitResults[0]
//get dict of same-color node
var dictOfSameColor = findAndReturnChain(boi: result.node)
// print(dictOfSameColor)
var finalNodes: [SCNNode] = [result.node]
var resFlag = 1
repeat {
var xSame: Bool = false
var ySame: Bool = false
var zSame: Bool = false
resFlag = 0
for node in finalNodes {
// var nodeX = node.position.x
for (key, value) in dictOfSameColor {
if(abs(node.position.x - value.position.x) < 0.7) {
xSame = true
}
if(abs(node.position.y - value.position.y) < 0.7) {
ySame = true
}
if(abs(node.position.z - value.position.z) < 0.7) {
zSame = true
}
//print("X-val: \(xDif) \nY-val: \(yDif) \nZ-val: \(zDif) \nColor: \(key.name) \n\n\n\n")
if (xSame && ySame ) {
if !(zSame) {
if (abs((node.position.z-value.position.z)) < 2) {
finalNodes.append(key)
dictOfSameColor.removeValue(forKey: key)
resFlag = 1
}
}
}
if (xSame && zSame) {
if !(ySame) {
if (abs((node.position.y-value.position.y)) < 2) {
finalNodes.append(key)
dictOfSameColor.removeValue(forKey: key)
resFlag = 1
}
}
}
if (ySame && zSame) {
if !(xSame) {
if (abs((node.position.x-value.position.x)) < 2) {
finalNodes.append(key)
dictOfSameColor.removeValue(forKey: key)
resFlag = 1
}
}
}
xSame = false
ySame = false
zSame = false
}
}
//print(finalNodes)
} while resFlag == 1
//print(finalNodes)
for node in finalNodes {
if node.name != "floor" {
node.removeFromParentNode()
}
}
//IMPLEMENT: Reset dicts to current state of the cube
myMasterCubeDict = updateMasterCubeDict(cube: gameCube)
dictOfSameColor.removeAll()
}
}
func findAndReturnChain(boi: SCNNode) -> [SCNNode:Cube.CubeDetails] {
var ret: [SCNNode:Cube.CubeDetails] = [:]
//find cubes with the same color
for (key, value) in myMasterCubeDict {
if value.color == boi.name {
ret[key] = value
}
}
return ret
}
func updateMasterCubeDict(cube: Cube) -> [SCNNode:Cube.CubeDetails] {
myMasterCubeDict.removeAll()
var newNode: SCNNode = SCNNode()
var newDetails = Cube.CubeDetails(color: "", position: SCNVector3Zero)
cube.enumerateChildNodes { (cube, stop) in
newNode = cube
if let newName = cube.name {
newDetails.color = newName
}
newDetails.position = cube.position
myMasterCubeDict[newNode] = newDetails
}
return myMasterCubeDict
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
}
解决方案
我做了一个有点像这样的游戏。您可能可以让数学工作,但我这样做的方式是映射出每个节点并有一个包含其相邻节点的数组。这样做,我确信当我删除一个节点并遍历它的相邻 [array] 节点时,我得到了正确的节点。
我没有将 SCNNodes 子类化——有些是这样,但我创建了我想要的包含我的节点信息的类——我将节点添加到 Scenekit 那里,这将实际节点与我可能想要对类做的其他工作分开。一些节点有很多我可能想要单独管理的细节(多个粒子系统、运动等)。然后我只是将我的节点类保存在一个数组中,每个类都可以直接访问它自己的节点。
抱歉 - 我不知道反弹,物理引擎有很多选择。
推荐阅读
- go - Go Reflection - 带有方法的动态类型
- concatenation - 可以在 Jolt 中连接来自两个不同对象的值
- debugging - 如何在 pine 脚本编辑器中查看错误/切换日志窗口?
- react-native - 在 redux-saga 中处理异步操作的最佳实践,因此使用基于 redux reducer 的 react-navigation 更改屏幕
- javascript - Pentaho CDE Javascript 更改表背景
- reactjs - 在没有主数的情况下操作小数
- r - 我可以将数据框中的 10 个逻辑列乘以一列,而忽略错误结果,例如矩阵吗?
- android - android从哪里获得补色?@color/mtrl_text_btn_text_color_selector
- android - 是否可以通过 Google Play 升级设备所有者应用程序?
- python - 为什么我的代码结果显示在调试控制台上,而通常它出现在终端上