簡體   English   中英

Scenekit:為什么不應該對 SCNNode 進行子類化?

[英]Scenekit: Why SCNNode should not be subclassed?

我一直在 StackOverflow 中讀到我們不應該將 SCNNode 子類化,有人可以為我指明 Scenekit 最佳實踐的正確方向嗎?

我覺得子類化 SCNNode 將幫助我擁有不同類型子類的特殊方法。 按照 Object 面向編程.. 可能是 SCNNode 的子類 3d Car 可能有啟動引擎、移動、打開門等的方法。

如果那不是正確的方法.. 如何將 SCNNodes 與額外的屬性和方法耦合? 而且,如何區分汽車 SCNNode 和卡車、飛機或其他任何 SCNNode?

我個人認為對SCNNode進行子類化沒有任何問題,這當然取決於您為什么需要這樣做。

這里的關鍵考慮因素如下:

如果要添加每個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可能是前進的方法。

假設我們需要用SCNPlaneGeometry創建一個SCNNode來為我們提供有關該Node的特定信息,然后我們可以創建一個子類,如下所示:

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 絕對、完全、正常。

我真的從未見過在任何項目中使用過“普通”SCNNode。 你會用它做什么?

SO 上大約有 5 條評論說“你不應該子類化 SCNNode”。 這五個評論是完全錯誤的。

Apple 在文檔中提到你不能將 SCN場景子類化......

這個 inte.net 神話似乎是由一個錯字引起的:

似乎在場景工具包的早期,蘋果開發者網站上的一位作者,在一篇關於 SCNScene 的帖子中,打錯了字,並在他們正在打字的時候寫了“你不能子類化一個節點”,因為這是不可能的對場景進行子類化

這被試圖在包括 SO 在內的不同論壇上寫答案的人復制了兩到三遍。

任何人都可以舉出無數關於節點子類化的例子:

///Node that shows a picture
public class PictureNode: SCNNode {
    
    ///Set the image shown on this PictureNode.
    public func setToImage(named: String) {
        geometry?.firstMaterial?.diffuse.contents = UIImage(named: named)
    }
    
    public func setToPlaceholderImage() { ...
    
    public func showWarningLabel() { ...
    
    public func animateToBlack() { ...
    
    public func reshapeNow(toAspectRatio: CGFloat) {
        thePlane.width = toAspectRatio
        thePlane.height = 1.0
    }
    
    private var pl = SCNPlane()
    
    override init() {
        super.init()
        
        pl.widthSegmentCount = 1
        pl... etc
        geometry = thePlane
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

{順便說一句,如果你想“子類化”場景,你可以子類SCNView ,這是你在每個場景工具包項目中所做的。}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM