FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

フレーム駆動設計で複雑なアニメーションに立ち向かう

こんにちは。寺坂です。  

複雑なアニメーションでも、突き詰めると

アニメーション == フレームの連続

になります。

一枚一枚のフレームをそのまま実装に落とし込むことができれば、

表現力はそのままに、シンプルなアプローチが可能となりますね。

今回はフレーム視点でアニメーションを整理し、iOSで使える CAKeyframeAnimation での実装を紹介します。

フレームから紐解く

右に移動、上に移動、大きくなる、消える、、、など、
様々な種類のアニメーションがありますが、

そのそれぞれに対して、

「変化フレーム」
「時間フレーム」

の2種類のフレームを考えながらアニメーションを整理します。

今回はアプリ版 GANMA! でお馴染みの

「ハートがふるえるアニメーション」
(以下、ハートアニメーション)

を例にして進めていきます。

f:id:i_terasaka:20170124130243g:plain

変化フレーム

変化フレームは、素材に対してどういう変化を加えるかを表します。

例えば、右に移動、上に移動など、アニメーションに必要な動きを書き出します。

ハートアニメーションの変化を分解して時系列に並べると、

  1. 通常の大きさ
  2. 縦に短く、横に長くなる
  3. 横に短く、縦に長くなる
  4. 縦に短く、横に長くなる
  5. 横に短く、縦に長くなる
  6. 縦に短く、横に長くなる
  7. 横に短く、縦に長くなる
  8. 通常の大きさ

となります。

縦や横に、計8個の変化フレームがありますね。

時間フレーム

時間フレームは、変化フレームに対してセットするものです。

ハートアニメーション全体が「10個の時間フレーム」を持っているとして、

これらを先ほどの8個の変化フレームに割り振ります。

ハートアニメーションは最初ゆっくりと、後半は素早く動くので、

  • 変化1~4 →ゆっくり
  • 変化4~8 →素早く

と決めます。

ゆっくりと動かすには、時間を多く与えてあげればいいので、

フレームの概念に置き換えると、

  • 変化1 →0フレーム使う →始まり位置
  • 変化2 →2フレーム使う →2/10で切り替わる
  • 変化3 →2フレーム使う →4/10で切り替わる
  • 変化4 →2フレーム使う →6/10で切り替わる
  • 変化5 →1フレーム使う →7/10で切り替わる
  • 変化6 →1フレーム使う →8/10で切り替わる
  • 変化7 →1フレーム使う →9/10で切り替わる
  • 変化8 →1フレーム使う →終わり位置

となります。

表に整理すると

以上のことを表にまとめると、次のようになります。

あとはこの情報を実装に当てはめていくだけです。

  • 形を変える → 縦横のscaleを変える
変化フレーム 時間フレームの個数 scale値: (x, y) 速さ
通常の大きさ 0 (1.0, 1.0) ゆっくり
縦を縮めて、横を伸ばす 0〜2 (1.3, 0.7) ゆっくり
横を縮めて、縦を伸ばす 2〜4 (0.8, 1.2) ゆっくり
縦を縮めて、横を伸ばす 3〜6 (1.25, 0.85) ゆっくり
横を縮めて、縦を伸ばす 7 (0.93, 1.08) 素早く
縦を縮めて、横を伸ばす 8 (1.1, 0.9) 素早く
横を縮めて、縦を伸ばす 9 (0.98, 1.03) 素早く
通常の大きさ 10 (1.0, 1.0) 素早く

フレームを実装に当てはめる

ここではiOSで用意されている

「CAKeyframeAnimation」

を使います。

これはその名の通り、アニメーションをフレーム単位でコントロールします。

基本的な部分だけで簡単に動きを表現できるので、とても便利な代物です。


それでは早速実装に落としていきます。

まずは形を変化をさせたいので、keyPathを"transform" に指定します。

let anim = CAKeyframeAnimation(keyPath: "transform")

次に変化フレームを実装するため、scale値をセットします。

anim.values = [
    NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(1.3, 0.7, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(0.8, 1.2, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(1.25, 0.85, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(0.93, 1.08, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(1.1, 0.9, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(0.98, 1.03, 1.0)),
    NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0))
]

最後に、上のscale値に対応させながら、時間フレームをセットしていきます。

anim.keyTimes = [
    0.0,
    0.2,
    0.4,
    0.6,
    0.7,
    0.8,
    0.9,
    1.0
]

durationとかアニメーションカーブを付け加えてまとめると、こうなります。

let anim = CAKeyframeAnimation(keyPath: "transform")
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
anim.repeatCount = .infinity
anim.duration = 1.5
anim.values = [
    NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)), // 0/10
    NSValue(caTransform3D: CATransform3DMakeScale(1.3, 0.7, 1.0)), // 2/10
    NSValue(caTransform3D: CATransform3DMakeScale(0.8, 1.2, 1.0)), // 4/10
    NSValue(caTransform3D: CATransform3DMakeScale(1.25, 0.85, 1.0)), // 6/10
    NSValue(caTransform3D: CATransform3DMakeScale(0.93, 1.08, 1.0)), // 7/10
    NSValue(caTransform3D: CATransform3DMakeScale(1.1, 0.9, 1.0)), // 8/10
    NSValue(caTransform3D: CATransform3DMakeScale(0.98, 1.03, 1.0)), // 9/10
    NSValue(caTransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0))  // 10/10
]
anim.keyTimes = [
    0.0,
    0.2,
    0.4,
    0.6,
    0.7,
    0.8,
    0.9,
    1.0
]

これだけでハートアニメーションを実装できました。簡単ですね!

変化の種類が複数あるアニメーションの場合

移動しながら大きさを変えるなど、
いろんな種類のアニメーションを組み合わせる場合もあります。

その時は、変化の種類ごとにフレームを考えると整理できると思います。

CAKeyframeAnimationで言えば、

  • サイズ変化用のCAKeyframeAnimation
  • 位置移動用のCAKeyframeAnimation
  • 色変化用のCAKeyframeAnimation

というように、それぞれの変化ごとにアニメーションを用意しておけば、最後にそれらをまとめて合成することができます。

// transaction開始
CATransaction.begin()

// アニメーションを追加する
layer.add(anim1, forKey: "opacity")
layer.add(anim2, forKey: "moveY")
layer.add(anim3, forKey: "moveX")

CATransaction.setCompletionBlock { _ in
    // 終了時の処理
}

// アニメーションスタート。ここが呼ばれるまでアニメーションはスタートしない
CATransaction.commit()

最後に

以上になります。

「フレーム駆動設計」と銘打ってきましたが、実はCAKeyframeAnimationの紹介が主旨だったりします。

フレームを整理した上でCAKeyframeAnimationを使えば、とても簡単にアニメーションを実装できるので、

是非使ってみてください!

最後までお読みいただき、ありがとうございました。

参考リンク

API Reference - CAKeyframeAnimation