iOS で SceneKit を試す(Swift 3) その7 - 標準的なアニメーション
SceneKit のアニメーション
SceneKit のアニメーションには大きく分けて以下の 2 種類があり、 今回の紹介するものは後者のもの。
- 物理アニメーションやパーティクルなど SceneKit が自動で動きを計算するもの
- アプリ開発者が任意で動きを設定するもの
アニメーションの方法の種類
SceneKit でアニメーションさせる主な方法は以下の3つ。
- Core Animation
- SCNAction
- SCNTransaction
Core Animation と SCNAction
チルドノードである SCNNode をアニメーションさせる。
UI を操作して弾を撃つ動作やキャラクターを移動させるなど、 ユーザーのインタラクションによるアニメーションに適している。
また、繰り返し設定があるため、固定している物体のアニメーションにも使用できる
SCNTransaction
シーン内のオブジェクトを一斉にアニメーションさせる。
ゲームオーバー時に画面を初期状態に戻すなどの全体の変更に適している。
| クラス名 | 適応箇所 | アニメーションの繰り返し |
|---|---|---|
| Core Animation | ノード | ○ |
| SCNAction | ノード | ○ |
| SCNTransaction | シーン | × |
ノードに紐付いているアニメーション機能しては、時間ないで処理する SCNAnimationEvent やキャラクターなどのスキニングアニメーションを行う SCNSkinne があるが今回は割愛。
以降、以前作成したテンプレートを使って、
ドーナツ状のオブジェクトを X 軸方向に回転のアニメーションを行う。
下準備
テンプレートを作成し、トーラス(ドーナツ状のもの)を作成。
全体の半径 2、わっかの断面の円の半径が 0.35 で設定した。
let torus = SCNTorus(ringRadius: 2, pipeRadius: 0.35) let torusNode = SCNNode(geometry: torus) self.rootNode.addChildNode(torusNode)
横を向いたドーナツが表示される。
現状だとドーナツなのか不明であるためアニメーションさせてみる。

Core Animation で動かす
通常の CABasicAnimation を設定して、SCNNode のメソッド addAnimation 適応するだけ。 以下の命令を addChildNode の下に設定。
let rotate = CABasicAnimation(keyPath: "rotation") rotate.fromValue = SCNVector4(0.0, 0.0, 0.0, 0.0) rotate.toValue = SCNVector4(1.0, 0.0, 0.0, Float.pi * 2.0) rotate.duration = 10 rotate.repeatCount = HUGE torusNode.addAnimation(rotate, forKey: "rotate")
forKey の文字列はアニメーションを止めるなど、今動作しているものを調べるためのもので必要なければ空でも良い。

SCNAction で動かす
SceneKit で用意されている SCNAction を使ってみる。
先ほどの Core Animation のものを削除し、以下の命令を addChildNode の下に設定。
X軸方向に 180度 回転し、その後 Z軸方向に180度回転する。
torusNode.runAction(
SCNAction.repeatForever(
SCNAction.sequence([
SCNAction.rotateBy(x: CGFloat(Float.pi * 1.0), y: 0, z: 0, duration: 5),
SCNAction.rotateBy(x: 0, y: 0, z: CGFloat(Float.pi * 1.0), duration: 5)
])
)
)
SpriteKit の SKAction と使い方は同じで以下の流れで動いている。
- 回転する命令 SCNAction.rotateBy を2つ設定
- SCNAction.sequence で設定したアニメーションを順に動かす設定をする
- SCNAction.repeatForever で無限ループにする
- SCNNode のメソッド runAction でアニメーションを開始
SCNTransaction で動かす
上の2つはノードに紐付いたメソッドだが、SCNTransaction はどこでも実行できる。 今回の例では、トーラスを360度回転させて、それと同時にカメラを遠ざけている。
先ほどの SCNAction のものを削除。
カメラとともに動かすため、以下のものを self.rootNode.addChildNode(cameraNode) よりの下に設定。
// アニメーション設定 開始 SCNTransaction.begin() SCNTransaction.animationDuration = 10 torusNode.rotation = SCNVector4(1.0, 0.0, 0.0, Float.pi * 2.0) cameraNode.position = SCNVector3(x: 0, y: 0, z: 30) // アニメーション設定 終了し実行 SCNTransaction.commit()
SCNTransaction はデフォルトでイージングが掛かっているので必要なければ切る。
アニメーションの同時設定、優先度
軽く調べたところ、同じノードに SCNAction と Core Animation を適応した場合は、SCNAction が優先される。
また、SCNAction か Core Animation でアニメーション設定したノードに対して、SCNTransaction でアニメーションをかけると互いの動作がミックスされる。
さわりだけの紹介なので、今後詳しく紹介したいと思う。
今回はここまで。
GameScene.swift の 全コード
Core Animation 版
import SceneKit
class GameScene: SCNScene {
override init() {
super.init()
self.setUpScene()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpScene() {
let torus = SCNTorus(ringRadius: 2, pipeRadius: 0.35)
let torusNode = SCNNode(geometry: torus)
self.rootNode.addChildNode(torusNode)
let rotate = CABasicAnimation(keyPath: "rotation")
rotate.fromValue = SCNVector4(0.0, 0.0, 0.0, 0.0)
rotate.toValue = SCNVector4(1.0, 0.0, 0.0, Float.pi * 2.0)
rotate.duration = 10
rotate.repeatCount = HUGE
torusNode.addAnimation(rotate, forKey: "rotate")
// オムニ ライト
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
self.rootNode.addChildNode(lightNode)
// アンビエント ライト
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
self.rootNode.addChildNode(ambientLightNode)
// カメラ
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
self.rootNode.addChildNode(cameraNode)
}
}
SCNAction 版
import SceneKit
class GameScene: SCNScene {
override init() {
super.init()
self.setUpScene()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpScene() {
let torus = SCNTorus(ringRadius: 2, pipeRadius: 0.35)
let torusNode = SCNNode(geometry: torus)
self.rootNode.addChildNode(torusNode)
torusNode.runAction(
SCNAction.repeatForever(
SCNAction.sequence([
SCNAction.rotateBy(x: 0, y: 0, z: CGFloat(Float.pi * 1.0), duration: 5),
SCNAction.rotateBy(x: CGFloat(Float.pi * 1.0), y: 0, z: 0, duration: 5)
])
)
)
// オムニ ライト
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
self.rootNode.addChildNode(lightNode)
// アンビエント ライト
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
self.rootNode.addChildNode(ambientLightNode)
// カメラ
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
self.rootNode.addChildNode(cameraNode)
}
}
SCNTransaction 版
import SceneKit
class GameScene: SCNScene {
override init() {
super.init()
self.setUpScene()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpScene() {
let torus = SCNTorus(ringRadius: 2, pipeRadius: 0.35)
let torusNode = SCNNode(geometry: torus)
self.rootNode.addChildNode(torusNode)
// オムニ ライト
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
self.rootNode.addChildNode(lightNode)
// アンビエント ライト
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
self.rootNode.addChildNode(ambientLightNode)
// カメラ
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
self.rootNode.addChildNode(cameraNode)
SCNTransaction.begin()
SCNTransaction.animationDuration = 10
torusNode.rotation = SCNVector4(1.0, 0.0, 0.0, Float.pi * 2.0)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 30)
SCNTransaction.commit()
}
}