【问题标题】:Scenekit: Why SCNNode should not be subclassed?Scenekit:为什么不应该对 SCNNode 进行子类化?
【发布时间】:2018-10-14 09:48:23
【问题描述】:

我一直在 StackOverflow 中看到我们不应该继承 SCNNode,有人可以为我指出 Scenekit 最佳实践的正确方向吗?

我觉得子类化 SCNNode 将帮助我拥有不同类型的子类的特殊方法。遵循面向对象编程..也许作为 SCNNode 子类的 3d Car 可以具有启动引擎、移动、打开门等的方法。

如果这不是正确的方法.. 如何将 SCNNodes 耦合到额外的属性和方法?还有,如何区分汽车的 SCNNode 和卡车、飞机或其他任何东西的 SCNNode?​​p>

【问题讨论】:

    标签: ios swift 3d scenekit arkit


    【解决方案1】:

    我个人认为子类化 SCNNode 没有任何问题,当然这取决于您为什么需要这样做。

    这里的一个关键考虑因素如下:

    如果您要添加应具备的通用功能 可用于每个 SCNNode,然后进行扩展。

    然后所有 SCNNode 实例都可以调用这些新方法。

    另一方面:

    如果您要添加的功能应该仅限于特殊 SCNNode 的实例,您需要具体识别这些实例:然后 做一个子类,因为只有这些实例可以使用你的新 方法。

    如果您选择使用SCNNode 的扩展,这意味着您创建的任何函数都可以应用于任何SCNNode

    例如,假设您希望允许任何SCNNode 增长和缩小,那么extension 将是您最好的选择,例如:

    extension SCNNode{
    
        /// Doubles The Size Of The SCNNode & Then Returns It To Its Original Size
        func growAndShrink(){
    
            //1. Create An SCNAction Which Will Double The Size Of Our Node
            let growAction = SCNAction.scale(by: 2, duration: 5)
    
            //2. Create Another SCNAction Wjich Will Revert Our Node Back To It's Original Size
            let shrinkAction = SCNAction.scale(by: 0.5, duration: 5)
    
            //3. Create An Animation Sequence Which Will Store Our Actions
            let animationSequence = SCNAction.sequence([growAction, shrinkAction])
    
            //4. Run The Sequence
            self.runAction(animationSequence)
    
        }
    
    }
    

    但是,例如,如果您想创建一个 SCNNode,它的功能只能用于该实例,那么创建一个 subclass 可能是前进的方向。

    假设我们需要创建一个带有SCNPlaneGeometrySCNNode,它为我们提供了有关该节点的特定信息,那么我们可以像这样创建一个子类:

    class PlaneNode: SCNNode {
    
        let DEFAULT_IMAGE: String = "defaultGrid"
        let NAME: String = "PlaneNode"
        var planeGeometry: SCNPlane
        var planeAnchor: ARPlaneAnchor
    
        var widthInfo: String!
        var heightInfo: String!
        var alignmentInfo: String!
    
        //---------------
        //MARK: LifeCycle
        //---------------
    
        /// Inititialization
        ///
        /// - Parameters:
        ///   - anchor: ARPlaneAnchor
        ///   - node: SCNNode
        ///   - node: Bool
        init(anchor: ARPlaneAnchor, node: SCNNode, image: Bool, identifier: Int, opacity: CGFloat = 0.25){
    
            //1. Create The SCNPlaneGeometry
            self.planeAnchor = anchor
            self.planeGeometry = SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))
            let planeNode = SCNNode(geometry: planeGeometry)
    
            super.init()
    
            //2. If The Image Bool Is True We Use The Default Image From The Assets Bundle
            let planeMaterial = SCNMaterial()
    
            if image{
    
                planeMaterial.diffuse.contents = UIImage(named: DEFAULT_IMAGE)
    
            }else{
    
                planeMaterial.diffuse.contents = UIColor.cyan
            }
    
            //3. Set The Geometries Contents
            self.planeGeometry.materials = [planeMaterial]
    
            //4. Set The Position Of The PlaneNode
            planeNode.simdPosition = float3(self.planeAnchor.center.x, 0, self.planeAnchor.center.z)
    
            //5. Rotate It On It's XAxis
            planeNode.eulerAngles.x = -.pi / 2
    
            //6. Set The Opacity Of The Node
            planeNode.opacity = opacity
    
            //7. Add The PlaneNode
            node.addChildNode(planeNode)
    
            //8. Set The Nodes ID
            node.name = "\(NAME) \(identifier)"
    
        }
    
        required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
    
        /// Updates The Size Of The Plane As & When The ARPlaneAnchor Has Been Updated
        ///
        /// - Parameter anchor: ARPlaneAnchor
        func update(_ anchor: ARPlaneAnchor) {
    
            self.planeAnchor = anchor
    
            self.planeGeometry.width = CGFloat(anchor.extent.x)
            self.planeGeometry.height = CGFloat(anchor.extent.z)
    
            self.position = SCNVector3Make(anchor.center.x, 0.01, anchor.center.z)
    
            returnPlaneInfo()
        }
    
        //-----------------------
        //MARK: Plane Information
        //-----------------------
    
        /// Returns The Size Of The ARPlaneAnchor & Its Alignment
        func returnPlaneInfo(){
    
            let widthOfPlane = self.planeAnchor.extent.x
            let heightOfPlane = self.planeAnchor.extent.z
    
            var planeAlignment: String!
    
            switch planeAnchor.alignment {
    
            case .horizontal:
                planeAlignment = "Horizontal"
            case .vertical:
                planeAlignment = "Vertical"
            }
    
            #if DEBUG
            print("""
                Width Of Plane =  \(String(format: "%.2fm", widthOfPlane))
                Height Of Plane =  \(String(format: "%.2fm", heightOfPlane))
                Plane Alignment = \(planeAlignment)
                """)
            #endif
    
            self.widthInfo = String(format: "%.2fm", widthOfPlane)
            self.heightInfo = String(format: "%.2fm", heightOfPlane)
            self.alignmentInfo = planeAlignment
        }
    
    }
    

    在您的情况下,似乎因为您计划有非常具体的实例,例如卡车、飞机等,每个都有自己的特定功能,那么使用 SCNNode 子类可能是前进的方向。

    希望对你有帮助...

    更新:根据您的要求,例如如果使用.scn file,这将如何工作?

    一些pseudo code 可能看起来像这样:

    /// Creates & Manages The Car Model
    class Car: SCNNode {
    
        let MODEL_SCALE = SCNVector3(0.5, 0.5, 0.5)
        let MODEL_POSITION = SCNVector3(1, 0, -2.5)
        let MODEL_ROTATION: CGFloat = 30.45
        let TURN_DURATION: Double = 1
    
        var leftFrontWheel: SCNNode!
        var rightFrontWheel: SCNNode!
        var leftBackWheel: SCNNode!
        var rightBackWheel: SCNNode!
    
        //--------------------
        //MARK: Initialization
        //--------------------
    
        override init() {
    
            super.init()
    
            //1. Get The Car Model From The Assetts Bundle
            guard let carModel = SCNScene(named: "StackOverflow.scnassets/Models/Car.scn"),
                let modelNode = carModel.rootNode.childNode(withName: "Root", recursively: false),
                let frontLeftWheel = modelNode.childNode(withName: "leftFront", recursively: false),
                let frontRightWheel = modelNode.childNode(withName: "rightFront", recursively: false),
                let rearLeftWheel = modelNode.childNode(withName: "leftRear", recursively: false),
                let rearRightWheel = modelNode.childNode(withName: "rightRear", recursively: false) else { return }
    
    
            //2. Scale, Rotate & Position The Car
            self.scale = MODEL_SCALE
            self.simdRotation = simd_float4 (0, 1, 0, Float(MODEL_ROTATION.degreesToRadians))
            self.position = MODEL_POSITION
    
            //2. Create A Reference To Each Wheel
            self.leftFrontWheel = frontLeftWheel
            self.rightFrontWheel = frontRightWheel
            self.leftBackWheel = rearLeftWheel
            self.rightBackWheel = rearRightWheel
    
    
            //3. Add The Car To The Root Node
            self.addChildNode(modelNode)
    
            print("""
                Loaded Car Model
                Scale = \(MODEL_SCALE)
                Rotation = \(MODEL_ROTATION)
                Position = \(MODEL_POSITION)
                """)
    
        }
    
        required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    
        //---------------
        //MARK: Animation
        //---------------
    
        /// Runs The Wheel Animation
        func animateWheels(){
    
            let wheelTurnAnimationOut = SCNAction.rotate(toAxisAngle:  SCNVector4(0 , 0 , 1, CGFloat(45).degreesToRadians), duration: TURN_DURATION)
            let wheelTurnAnimationIn = SCNAction.rotate(toAxisAngle:  SCNVector4(0 , 0 , 1, CGFloat(0).degreesToRadians), duration: TURN_DURATION)
            let turningSequence = SCNAction.sequence([wheelTurnAnimationOut, wheelTurnAnimationIn])
            let turningAction = SCNAction.repeatForever(turningSequence)
            leftFrontWheel.runAction(turningAction)
            rightFrontWheel.runAction(turningAction)
            leftBackWheel.runAction(turningAction)
            rightBackWheel.runAction(turningAction)
    
        }
    
    }
    

    然后您可以像这样初始化和管理函数:

     let car = Car()
     self.augmentedRealityView.scene.rootNode.addChildNode(car)
     car.animateWheels()
    

    希望对你有帮助...

    【讨论】:

    • 我明白了。如果 SCNNode 来自 .scn 文件,这将如何工作?
    • ? 非常有趣,这很有帮助。感谢您提供如此完整的答案
    • 很高兴它有帮助:)
    • 由于某种原因,标准 init 似乎已从 SCNNode 中删除,这意味着它现在没有指定的初始化器适合在子类中使用.... edit 或至少在某些情况下它无法工作......
    • @Ash,嗯,我刚刚在 Xcode 10.2 中创建了一个快速测试,似乎工作正常 :) 你使用的是什么版本?
    猜你喜欢
    • 2017-05-16
    • 2012-10-25
    • 2015-06-19
    • 1970-01-01
    • 2017-06-28
    • 1970-01-01
    • 2010-09-13
    • 2018-03-28
    • 2019-10-04
    相关资源
    最近更新 更多