Scenekit:为什么不应该对 SCNNode 进行子类化?

Scenekit: Why SCNNode should not be subclassed?

我一直在 Whosebug 中读到我们不应该将 SCNNode 子类化,有人可以为我指明 Scenekit 最佳实践的正确方向吗?

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

如果那不是正确的方法..如何将 SCNNodes 耦合到额外的属性和方法?而且,如何区分汽车 SCNNode 和卡车、飞机或其他任何 SCNNode?

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

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

If you are adding general purpose functionalities that should be available to every SCNNode, then make an extension.

All SCNNode instances can then call these new methods.

另一方面:

If you are adding functionality that should be restricted to special instances of SCNNode, and you need to identify these specifically: then make a subclass, since only instances of these can use your new methods.

如果您选择使用 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 可能是前进的方向。

假设我们需要创建一个 SCNNode 和一个 SCNPlaneGeometry 来为我们提供关于该节点的特定信息,然后我们可以创建一个子类,如下所示:

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: "Whosebug.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()

希望对您有所帮助...