前言
前面几章,SCNView
的scene
都是使用默认情况下的。在这里,介绍另外一个创建方法,需要指定加载的资源。
func scene(options: [SCNSceneSource.LoadingOption : Any]? = nil, statusHandler: SceneKit.SCNSceneSourceStatusHandler? = nil)
*demo1
import UIKit
import SceneKit
//展示3D模型并且施加动画
class ViewController: UIViewController {
fileprivate lazy var sceneView: SCNView = {
let sceneView = SCNView()
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.black
return sceneView
}()
fileprivate lazy var purpleButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 120, height: 50)
button.setTitle("紫色", for: .normal)
button.setTitleColor(UIColor.purple, for: .normal)
button.backgroundColor = UIColor.white
return button
}()
fileprivate lazy var blackButton: UIButton = {
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 120, height: 50)
button.setTitle("黑色", for: .normal)
button.setTitleColor(UIColor.red, for: .normal)
button.backgroundColor = UIColor.white
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.frame = view.bounds
let url = Bundle.main.url(forResource: "skinning", withExtension: "dae")
// 强制解包,实际应用中不能这么处理
let sceneSource = SCNSceneSource(url: url!, options: nil)
// 指定资源
sceneView.scene = sceneSource?.scene(options: nil)
view.addSubview(sceneView)
// 动画
if let animalIDs = sceneSource?.identifiersOfEntries(withClass: CAAnimation.self){
var array:[CAAnimation] = []
var maxDuration: Float = 0
for i in 0..<animalIDs.count {
let animation = sceneSource?.entryWithIdentifier(animalIDs[i], withClass: CAAnimation.self)
array.append(animation!)
maxDuration = max(maxDuration, Float(animation!.duration))
}
let longAnimalGroup = CAAnimationGroup()
longAnimalGroup.animations = array
longAnimalGroup.duration = CFTimeInterval(maxDuration)
let lastAnimalGroup = CAAnimationGroup()
lastAnimalGroup.timeOffset = 20
lastAnimalGroup.duration = CFTimeInterval(maxDuration)
lastAnimalGroup.repeatCount = MAXFLOAT
lastAnimalGroup.autoreverses = true
//指定某一个节点的动作重复
let personNode = sceneView.scene?.rootNode.childNode(withName: "avatar_attach", recursively: true)
personNode?.addAnimation(lastAnimalGroup, forKey: "animation")
}
purpleButton.center = CGPoint(x: view.center.x, y: view.center.y + 140)
blackButton.center = CGPoint(x: view.center.x, y: view.center.y + 200)
view.addSubview(purpleButton)
view.addSubview(blackButton)
purpleButton.addTarget(self, action: #selector(purpleClick), for: .touchUpInside)
blackButton.addTarget(self, action: #selector(blackClick), for: .touchUpInside)
}
@objc func purpleClick(){
// 更换衣服节点处的内容
let shirtNode = sceneView.scene?.rootNode.childNode(withName: "shirt", recursively: true)
shirtNode?.geometry?.firstMaterial?.diffuse.contents = "export_0_texture19.png"
}
@objc func blackClick() {
let shirtNode = sceneView.scene?.rootNode.childNode(withName: "shirt", recursively: true)
shirtNode?.geometry?.firstMaterial?.diffuse.contents = "export_0_texture22.png"
}
}
运行效果如下:
*demo2
import UIKit
import SceneKit
class ViewController: UIViewController {
@IBOutlet weak var scnView: SCNView!
var sunCameraNode: SCNNode?
var earthCameraNode: SCNNode?
override func viewDidLoad() {
super.viewDidLoad()
scnView.backgroundColor = UIColor.black
scnView.scene = SCNScene()
// 太阳
let sunNode = SCNNode()
sunNode.geometry = SCNSphere(radius: 3)
sunNode.geometry?.firstMaterial?.diffuse.contents = "sun.jpg"
scnView.scene?.rootNode.addChildNode(sunNode)
// 太阳自传
let sunAction = SCNAction.repeatForever(SCNAction.rotate(by: 0.1, around: SCNVector3(0, 1, 0), duration: 0.5))
sunNode.runAction(sunAction)
//照相机视角1(太阳正前方)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 0, 40)
cameraNode.camera?.automaticallyAdjustsZRange = true
scnView.scene?.rootNode.addChildNode(cameraNode)
//指定观察点
scnView.pointOfView = cameraNode
self.sunCameraNode = cameraNode
//地球:直接把地球节点加在太阳上,在视角1的情况下正常。但是当切换视角2的时候,地球禁止不动,太阳也会出现,只有月球轨迹正常,所以考虑,不要直接把地球加到太阳的节点上去,创建一个过渡的节点来
let transitionNode = SCNNode()
transitionNode.position = SCNVector3(10, 0, 0)
sunNode.addChildNode(transitionNode)
let earthNode = SCNNode()
earthNode.geometry = SCNSphere(radius: 1)
earthNode.geometry?.firstMaterial?.diffuse.contents = "earth.jpg"
earthNode.position = SCNVector3(0, 0, 0)
transitionNode.addChildNode(earthNode)
// earthNode.position = SCNVector3(10, 0, 0)
// sunNode.addChildNode(earthNode)
//地球自传+公转
let earthAction = SCNAction.repeatForever(SCNAction.rotate(by: 0.1, around: SCNVector3(0, 1, 0), duration: 0.3))
earthNode.runAction(earthAction)
//照相机视角2(地球正前方)
let cameraNode1 = SCNNode()
cameraNode1.camera = SCNCamera()
cameraNode1.position = SCNVector3(0, 0, 10)
cameraNode1.camera?.automaticallyAdjustsZRange = true
// earthNode.addChildNode(cameraNode1)
transitionNode.addChildNode(cameraNode1)
self.earthCameraNode = cameraNode1
//月球
let moonNode = SCNNode()
moonNode.geometry = SCNSphere(radius: 0.5)
moonNode.geometry?.firstMaterial?.diffuse.contents = "moon.jpg"
moonNode.position = SCNVector3(2, 0, 0)
earthNode.addChildNode(moonNode)
//月球自传+公转
let moonAction = SCNAction.repeatForever(SCNAction.rotate(by: 0.1, around: SCNVector3(0, 1, 0), duration: 0.1))
moonNode.runAction(moonAction)
}
@IBAction func universeAction(_ sender: Any) {
let action = SCNAction.repeatForever(SCNAction.move(to: SCNVector3(0, 0, 100), duration: 1))
self.sunCameraNode?.runAction(action)
//切换观察点
self.scnView.pointOfView = self.sunCameraNode
}
@IBAction func sunAction(_ sender: Any) {
let action = SCNAction.repeatForever(SCNAction.move(to: SCNVector3(0, 0, 40), duration: 1))
self.sunCameraNode?.runAction(action)
//切换观察点
self.scnView.pointOfView = self.sunCameraNode
}
@IBAction func earthAction(_ sender: Any) {
//切换观察点
self.scnView.pointOfView = self.earthCameraNode
}
}
运行效果如下:
比较demo1
和demo2
发现,demo1
使用的是CAAnimation
来促使节点运动,而demo2
使用的是SCNAction
,简单的从点到点的运动可以使用SCNAction
,而稍微复杂自定义的动画使用CAAnimation