ios Scenekit:为什么SCNNode不应该被子类化?

1szpjjfi  于 2022-11-19  发布在  iOS
关注(0)|答案(2)|浏览(182)

我一直在StackOverflow中阅读到我们不应该子类化SCNNode,有人能给我指出Scenekit最佳实践的正确方向吗?
我觉得将一个SCNNode子类化将帮助我拥有不同类型子类的特殊方法。按照面向对象编程...也许一辆3D汽车是SCNNode的子类,它可以拥有启动引擎、移动、开门等方法。
如果这不是正确的方法...如何将SCNNode与额外的属性和方法耦合?还有,如何区分SCNNode是汽车,还是卡车、飞机或其他任何东西?

5lhxktic

5lhxktic1#

我个人并不认为对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创建一个SCNNodeSCNPlaneGeometry为我们提供了关于该节点的特定信息,那么我们可以创建一个子类,如下所示:

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()

希望能帮上忙...

qij5mzcb

qij5mzcb2#

对SCNNode进行子类化是绝对、完全、正常的。

我真的 * 从来没有见过 * 一个“普通”SCNNode在任何项目中使用。你会用它做什么?

在S.O.上有大约五条评论说“你不应该子类化SCNNode”。这五条评论完全错误。

Apple在一份文档中提到,您不能对SCN***场景***...

这个网络流言似乎是由一个打字错误引发的:

似乎在scene kit的早期,苹果开发者网站上的一位作者在一篇关于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")
    }
}

{As顺便说一句,如果您想“子类化”Scene,您需要子类化SCNView,这是您在每个Scene Kit项目中所做。}

相关问题