Apple Engine

Apple, iPhone, iOS, その周辺のことについて

iOS で SceneKit を試す(Swift 3) その8 - SCNAction でアニメーション設定

Core Animation を使用するより SCNAction を使った方が便利なのでこちらを使用。

SpriteKit の SKAction の3次元版なので使用方法はほぼ同じ。

 

SCNAction での SCNVector3 と SCNVector4

SCNVector3 は x, y, z の3つの値を持ち値の方向に移動または回転させる際に使用。

SCNVector4 は x, y, z、w の4つの値を持ち w の成分が x, y, z のベクトル値に反映される。
回転させる際に使用。

SCNAction では x、y、z で値を指定できるため SCNVector3 や SCNVector4 は使用しなくてもアニメーションは可能。

 

SCNAction とは

SceneKit 用のアニメーションライブラリのようなもの。
SCNAction で設定し、SCNNode でそのアニメーション設定を実行する。

以下、SCNAction 設定可能な振る舞い。

  • 移動
  • 回転
  • 拡大
  • フェードイン / フェードアウト
  • 表示 / 非表示
  • 一時停止
  • 逆再生
  • ノードの削除
  • アニメーションのグループ化
  • アニメーションを順番に再生する
  • アニメーションのリピート
  • カスタムアニメーションの設定
  • 効果音など音の再生

SCNNode に SCNActionable のプロトコルが適応されており、SCNAction で設定したアクションの再生や削除、アクションを調べるものがある。

また、SCNAction のメソッドは全てクラスメソッドになっている。 

 

テンプレートをつくる

以前のテンプレートを使い GameScene.swift を修正。
func setUpScene() の下に以下のコートを入力し立方体のジオメトリを追加。

let box = SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0.2)
let boxNode = SCNNode(geometry: box)
self.rootNode.addChildNode(boxNode)

ビルドすると立方体が描画される。

 

アニメーションの再生方法

ノードが持つメソッド runAction で設定するアクションを呼ぶと、即時でアニメーションされる。
そのため、定数に SCNAction を設定しておくとアニメーションの動作を使い回しできる。

 

移動

移動には目標座標まで進む to と 現在位置から移動を決める by のメソッドがある。
to は移動させる場所が決まっているもの、
by は UI からキャラクターなどを移動させるものに適している。

SCNAction.moveBy - x, y ,z で指定

以下のものを boxNode の宣言以下のものを書く。 x に 2 を入れ X 軸方向に プラス2 移動させており、
duration はアニメーション終了するまでの秒数。

let action = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(action)

 

by の値は移動量なのでもう一度行うと X軸 4 の座標へ移動する。

let action1 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(action1)
let action2 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(action2)

 

SCNAction.moveBy - SCNVector3 で指定

上と同じものを SCNVector3 で書いたのもの

let action = SCNAction.move(by: SCNVector3(2, 0 ,0), duration: 2)
boxNode.runAction(action)

 

SCNAction.moveTo

以下のものを書くと X 座標 2 まで移動する

let action = SCNAction.move(to: SCNVector3(2, 0 ,0), duration: 2)
boxNode.runAction(action)

 

必ず、X 座標 2 へ移動させるため、by のように2回行っても 4 の座標には進まない。

let action1 = SCNAction.move(to: SCNVector3(2, 0 ,0), duration: 2)
boxNode.runAction(action1)
let action2 = SCNAction.move(to: SCNVector3(2, 0 ,0), duration: 2)
boxNode.runAction(action2)

 

to で移動する座標はローカル座標である。 そのため、SCNNode 入れ子にした場合、親の座標が X 軸 -1 にいて、 子が X 方向 2 移動した場合はワールド座標では X 方向 1 へ移動する。

 

回転

移動と同様に by と to のメソッドがあり、プラス値で反時計回りに回る。

SCNAction.rotateBy - x, y ,z で指定

以下、x, y ,z に回転させたい角度をラジアンで入れて、duration で 2 秒を設定し、2 秒で Z 軸方向 45 度回転させている。 今回は 角度 × ( π / 180 ) で角度を出している。 こちらのメソッドは各軸の角度が合成されるので

let action = SCNAction.rotateBy(x: 0, y: 0, z: CGFloat(45 * (Float.pi / 180)), duration: 2)
boxNode.runAction(action)

 

SCNAction.rotateTo - x, y ,z で指定

上の to のバージョン。 移動同様に指定された角度に回転するため、45 度以上にはならない。

let action = SCNAction.rotateTo(x: 0, y: 0, z: CGFloat(45 * (Float.pi / 180)), duration: 2)
boxNode.runAction(action)

 

SCNAction.rotateBy - SCNVector3 で指定

by の回転の SCNVector3 のバージョン。
上のと同じように 2 秒で Z 軸方向 45 度回転させており、 by で回転角度、around で回転する軸、duration で 2 秒を設定している。

let action = SCNAction.rotate(by: CGFloat(45 * (Float.pi / 180)), around: SCNVector3(0,0,1), duration: 2)
boxNode.runAction(action)

 

SCNAction.rotateTo - SCNVector4 で指定

to の回転の SCNVector4 のバージョン。 上のと同じように 2 秒で Z 軸方向 45 度に回転させており、 toAxisAngle の SCNVector4 は4番目の w を前の3 引数 x,y, z に適応させている。

let action = SCNAction.rotate(toAxisAngle: SCNVector4(0, 0, 1, 45 * (Float.pi / 180)), duration: 2)
boxNode.runAction(action)

 

SCNAction.rotateTo - ShortestUnitArc を指定

このメソッドを使用すると現在地から最も近い回転位置で止まる。 以下のコードでは 450 度、1周と90度回転するはずですが、 usesShortestUnitArc が true になっているため、 最も近い位置の 90 度の位置で止まる。 usesShortestUnitArc を false にするとデフォルト設定に戻るため 450 度回転する。

let action2 = SCNAction.rotateTo(x: 0, y: 0, z:  CGFloat(450 * (Float.pi / 180)), duration: 2, usesShortestUnitArc: true)
boxNode.runAction(action2)

 

拡大縮小

移動と同様に by と to のメソッドがあり、x, y, z を1つの値で設定する デフォルト値は 1 で、0 にすると表示されなくなる。

マイナスを指定すると法線が逆になり、表示がおかしくなる可能性があるので注意。

SCNAction.scaleBy

現在の大きさから 2 秒で 2 倍の大きさにする。

let action = SCNAction.scale(by: 2, duration: 2)
boxNode.runAction(action)

 

SCNAction.scaleTo

初期の大きさから 2 秒で 2 倍の大きさにする。
ノードの x, y, z スケールのいずれかが to の値より大きい場合、 アニメーションせずに即時でどの大きさになる。 (バグ?)

let action = SCNAction.scale(to: 2, duration: 2)
boxNode.runAction(action)

 

フェードイン / フェードアウト

完全に透明度が 0 や 1 になるメソッドと、透明度を指定するものがあり、
移動と同様に透明度指定も to と by がある。

SCNAction.fadeOut - 徐々に消える

2秒で透明度を0にして見えなくする

let action = SCNAction.fadeOut(duration: 2)
boxNode.runAction(action)

 

SCNAction.fadeIn - 徐々に表示させる

2秒で透明度を1にして表示する。
ノードの透明度は1なので、最初に opacity で透明度を 0 にする

boxNode.opacity = 0
let action = SCNAction.fadeIn(duration: 2)
boxNode.runAction(action)

 

SCNAction.fadeOpacity - by

現在の透明度の値から 0.8 引いて 20% の透明度にする。

let action = SCNAction.fadeOpacity(by: -0.8, duration: 2)
boxNode.runAction(action)

 

SCNAction.fadeOpacity - to

現在の透明度の値から 20% の透明度にする。

let action = SCNAction.fadeOpacity(to: 0.2, duration: 2)
boxNode.runAction(action)

 

表示 / 非表示

ノードを表示 / 非表示する 内部的には SCNNode の isHidden が false か true に設定される。

let action = SCNAction.hide()
boxNode.runAction(action)
let action = SCNAction.unhide()
boxNode.runAction(action)

 

逆再生

逆再生と書いたが API Refalence 的には reversed() を呼ぶことで戻す値で実行される。 そのため、以下のコードを入れると -X 軸方向に移動する。

後ほど説明するシーケンスのメソッドで使用すると観覧にループの動作が作成できる。

let action = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(action.reversed())

 

一時停止

アニメーションを一時停止させるために使用する。 以下、runAction の completionHandler を使用しているが、後ほど説明するシーケンスで使用するケースが多いと思われる。

一時停止する

2秒停止して、その後 X 軸方向に 2 移動する。

let action1 = SCNAction.wait(duration: 2)
let action2 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(action1, completionHandler: {
    boxNode.runAction(action2)
})

 

一時停止の振り幅を決める

withRange を設定することで一時停止の時間に揺らぎを持たせることができる。

以下のサンプルでは、待ち時間 2 秒かつ ± 1秒ランダムの終了時間となり、 1秒〜3秒の待ち時間となる。

let action1 = SCNAction.wait(duration: 2, withRange: 2)
let action2 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(action1, completionHandler: {
    boxNode.runAction(action2)
})

 

アニメーションのグループ化

SCNAction をグループ化し、複数の SCNAction を同時に再生する。
SCNAction.group メソッドに SCNAction の配列を入れるだけ。

以下の例では拡大と移動が同時に行われる。

let action1 = SCNAction.scale(by:2, duration: 2)
let action2 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
boxNode.runAction(
    SCNAction.group([
        action1,
        action2
    ])
)

 

アニメーションを順番に再生する

複数の SCNAction を順番に再生する。 SCNAction.sequence メソッドに SCNAction の配列を入れるだけ。

以下の例では X軸方向に 2 移動し、2秒待機、reversed メソッドで元に位置に戻る。

let action1 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
let action2 = SCNAction.wait(duration: 2)
boxNode.runAction(
    SCNAction.sequence([
        action1,
        action2,
        action1.reversed()
    ])
)

 

アニメーションのリピート

指定回数分リピートするアニメーションと、無限にリピートするアニメーションする設定がある。

指定した回数だけリピートする

上のシーケンスを使用したアニメーションを SCNAction.repeat で囲み、 count を 4 に設定し 4 回繰り返す。

let action1 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
let action2 = SCNAction.wait(duration: 2)
boxNode.runAction(
    SCNAction.repeat(
        SCNAction.sequence([
            action1,
            action2,
            action1.reversed()
        ]),
        count: 4
    )
)       

 

無限にリピートする
let action1 = SCNAction.moveBy(x: 2, y: 0, z: 0, duration: 2)
let action2 = SCNAction.wait(duration: 0.5)
boxNode.runAction(
    SCNAction.repeatForever(
        SCNAction.sequence([
            action1,
            action2,
            action1.reversed()
        ])
    )
)

 

ノードの削除

SCNAction に紐づいている SCNNode をシーンから消すことができる。 以下のサンプルでは scale で 4 倍の大きさにして、アニメーション終了後にシーンから消す。

let action = SCNAction.scale(by: 4, duration: 2)
boxNode.runAction(
    SCNAction.sequence([
        action,
        SCNAction.removeFromParentNode()
    ])
)

 

長くなったので、カスタムアニメーションと音の再生は割愛。

今回はここまで。