ios - 如何动态跟踪场景中的 SCN 节点以进行删除?

标签 ios swift scenekit game-physics

必填:第一次编写应用程序,相关代码如下。我的代码大部分按照我想要的方式运行,但没有实现动态对象跟踪的目标。

我正在使用 Swift 和 Scenekit 构建一个简单的益智游戏,类似于 3D 版本的糖果粉碎传奇。

我有一个类Cube,它扩展了SCNNode。初始化时,此类将随机绘制一个由 SCNBoxes 组成的 5x5 立方体,每个框都是红色、绿色或蓝色(框的所有 6 个面都是 1 种颜色)。

游戏的目标是通过移除颜色相似的 SCNBox 的“链”来获得最高分。当链条被移除时,立方体应该识别重力并掉落以填充被移除的链条所产生的空隙。这是我需要动态跟踪位置的地方。当立方体落入间隙时,它们的邻居就会发生变化。

我的方法:构建一个具有属性 var color: Stringvar location: SCNVector3struct CubeDetails。接下来,构建一个字典 masterCubeDict = [SCNNode: CubeDetails],其中包含 1 种颜色的所有立方体(颜色由 hittestresult 提供)。

每次用户点击一个立方体,抓取它的颜色,刷新 masterCubeDict,然后使用 SCNVector3 位置上的数学来确定哪些立方体是邻居。

我认为我使用 scnvector3 上的数学来查找“立方体邻居”的算法就是我的出发点。一定有更好的方法让 scenekit 节点相互识别/查找,对吗?

另外——我希望立方体的物理特性能够让它们下落并且根本没有弹跳/滑动。它们应该只能直线上/下移动。碰撞绝对不应该发生。我认为我通过摩擦力、恢复力和立方体的质量正确地实现了这一点,但我没有得到我想要的结果。

立方体类

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 那里,这将实际节点与我可能想做的其他工作分开类(class)。有些节点有很多我可能想要单独管理的细节(多个粒子系统、运动等)。然后,我将节点类保存在一个数组中,每个类都可以直接访问它自己的节点。

抱歉 - 我不知道弹跳,物理引擎有很多选择。

关于ios - 如何动态跟踪场景中的 SCN 节点以进行删除?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59813972/

相关文章:

ios - swift : How to put string in\( )?

ios - UITableView 的网络优化

ios - supportedInterfaceOrientationsForWindow 未在 iPad 中调用

ios - 匹配的几何效果向后动画似乎在 View 后面

swift - 如何调暗Scene Kit或ARkit中Camera渲染的 View 中的光线?

ios - 如何将 ARAnchor 大小转换为 SpriteKit 等效大小

swift - 是否可以在没有平面检测的情况下通过 HitTest 放置物体?

ios - 如何更换来电时的默认提醒?

ios - ios5中的自动旋转——失败的最常见原因?

ios - 在 Ionic 中,是否可以在内部发布 ios?