繁体   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