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()
希望对您有所帮助...
我一直在 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()
希望对您有所帮助...