この記事は KLab Advent Calendar 2017 8日目の記事です。

こんにちは knsh14 です。 Unity で AnimationClip Editor を作った話をします。

KLab では様々な演出などで AnimationClip を使っていますが、Unity 標準の AnimationClip のエディタはどうにも使いづらいものになっています。

  • 各オブジェクトの並べ方が幅優先表示になっていて非常に見づらい
  • ショートカットがほとんど無い
  • 編集にじゃまなパーツを折りたたんで非表示にできない

などなどあります。 既存のアセット等でも対応できなさそうだったので、今回内製でエディタを作成することになりました。

実際の画面

sample

機能

エディタというのは単純だと思っていてもいろいろな機能があります。

エディタとしての機能

  • 新規作成、読み込み
  • 前回保存分を再開する
  • AnimationClip の情報の編集
  • AnimationCurve への KeyFrame の追加削除、値の編集
    • 変更はその場で反映される
  • キーのコピペ
  • キーを範囲選択して左右へ移動させる
  • 操作の Undo/Redo
  • データの保存

AnimationClip を編集するための機能

  • 今の状態を自動でプレビューできるようにする
  • シーン側でオブジェクトを動かしたりしたらエディタのキーフレームに反映される

基本設計

クラスやプロジェクト構成などは Clean Architecture を参考に作成しました。 Unity Editor 拡張で説明するより、 iOS/Android で解説されている資料のほうがより詳細にかかれていてわかりやすいと思うので、そちらを参考にしていただくのが一番いいと思います。

以下のような構成でできている仕組みです。

  1. Presentation 層

    1. View
      • UI を構築します。
      • UnityEditor.EditorWindow を継承し、アプリケーション全体の起動も担当します。
    2. ViewModel
      • Data 層の Entity を View で表示するためのクラスです
    3. Presenter (Controller)
      • View から受け取ったイベントを UseCase に流して処理してもらったり、データを取得したりする。
      • 本来なら Controller がイベントを受け取り UseCase に流す、 Presenter がデータを取得するという役割分担までするのですが、一旦まとめて実装しました。
  2. UseCase 層

    1. UseCase
      • 固有のロジックを実装します
    2. Translator
      • Entity を受け取り、Presentation 層で使う ViewModel に変換します。
      • Presentation から受け取ったものを Entity で扱えるようにしているところもあります。
  3. Data 層

    1. Repository

      • UseCase と DataStore の遣り取りをするための薄いインターフェース
    2. DataStore

      • Entity の集合で CRUD を実装します
      • DataStore をシリアライズして JSON に保存することで前回の状態から作業を再開できるような仕組みも実装できます。
    3. Entity

      • コアのデータ定義をします。
      • 例えば GameObject の ID、アニメーションするプロパティ、AnimationCurve のセットやショートカット名と実行するキーの組み合わせなどです

こうすることで以下のようなメリットがあります。

  1. UI と裏側のロジックを完全に分離できる

    • 特に Unity EditorWindow のイベントハンドリングは中々癖が強いので出来る限り一元管理できる仕組みにできたほうが都合がいいです。
  2. テストしやすい

    • 操作を UI から切り離しているので、操作ごとにテストを走らせるなんてこともできます
    • このエディタは一人で作っていたので簡単にテストできる仕組みは動作を安定させるために必須項目でした
  3. コードの再利用がしやすい

    • ショートカット機能では利用者が好きなようにショートカットの割当をできるのですが、これは別のウィンドウで行います。
    • そのために似た実装が増えるのは大変だし、共通化することでどちらかでバグが出たらすぐに気づけるようにしました。
  4. 実装にブレが出にくい

    • この領域では何をするかが割りときっちり決まっているので、実装にブレが出づらいです。
    • また後でメンテナンスする場合にも読みやすさが高くなります。

デメリットもあって、次のようなものです。

  1. ちゃんと理解してないとどこに何を書くのかわかりづらい

    • これは MVC などでも言えることだと思いますが、設計をしっかり理解してからじゃないとうまくかけないのはちょっとハードルが高いです。
  2. 当然ですがコードが長くなる

    • 強いて言えば程度のデメリットですが、当然たくさんコードを書かないといけません。

エディタを実装する

全ての機能を紹介するのは大変なので一部だけ抜粋してどのように実装したか紹介します。

Undo/Redo

Undo/Redo は自前で実装するのは結構たいへんですが、Unity には割りと簡単に Undo をサポートする仕組みがあります。 Undo したいオブジェクトをシリアライズされる状態にして、UnityEngine.Object から辿れるようにします。 UnityEditor.Undo.RecordObject(UnityEngine.Object obj, string title) で操作を記憶してやると、その操作が Undo スタックに乗って、Unity のショートカットで Undo/Redo することができます。

範囲選択してコピペ

エディタといえばコピペですね! ペーストは clipboard にあるものを取り出してキーを追加する操作をすればいいので簡単なのですが、意外と大変なのがコピーです。

コピー操作はキーフレームを clipboard に入れることで実装できます。 ただコピーと一口に言ってもいろいろなコピー対象があります。

  • パーツについてるプロパティのキー全体をコピー
  • パーツについてるプロパティのキーの一部をコピー
  • パーツについてる一部のプロパティのキー全体をコピー
  • パーツについてる一部のプロパティのキーの一部をコピー
  • フレキシブルに選択した範囲をコピー

この種類を全部対応するのは大変そうだったので、最後の「事前に範囲選択をしてからその領域にあるキーフレームをコピー」だけを実装することにしました。 編集する対象によってこの辺は変わることが多いと思うのでどの仕組みにも対応できるように作っておけると楽になると思います。

ショートカット

ショートカットは利用していただいてるデザイナの方にも非常に好評で作ってよかった機能です。

仕組みは簡単で

  1. View にショートカットに対応しているメソッドを用意
  2. キーイベントからキー入力と突き合わせて一致したメソッド名を取得
  3. リフレクションで実行

というフローになっています。
ただこれには問題点があって、これだと1発のキー入力しかショートカットに登録できません。
Ctrl-X Ctrl-O のような2段階ショートカットに対応するには、一旦キー入力をキューに保存するなどの工夫が必要になります。

苦労したところ

UIレイアウトがなかなか直感的に書きづらい

全部 C# で書けるのは割りと楽なことも多いのですが、HTML で書いてこの要素にイベント仕込むだけならもっとUI作成も楽なのになあと思うことも多少あります。 またレイアウトではどうしても右寄せ左寄せなどの配置が難しいので、キーフレームの描画などは苦労することもありました。

またいろいろなデータを描画しようと思うと描画順で頭を悩ませることもありました。
処理的にはここでひとまとめに書くのがシンプルなんだけど描画を考えるとあとでわざわざ描くといった苦しいコードになることもありました。
depth 欲しいです。

Unity Editor 拡張のイベントハンドリングは辛い

Unity のイベントハンドリングは UnityEditor.Event.current を見てハンドリングするのですが、個別のUIに操作を割り当てるのがかなり苦手な感じを受けました。   ボタンやUIの値が変更された場合は割りと簡単に個別のハンドリングができるのですが、ドラッグやショートカット用のキーイベントなどになると途端にシンプルに書きづらくなるので、注意する必要があります。

終わりに

今回は Unity の Editor 拡張でエディタを作るという、Unity を使っているだけならなかなかやらないような話をしました。   ちゃんとしたエディタを一通り作るのは Unity の力を借りてもかなり大変でした。 普段は何気なくやっている操作も色々試行錯誤がある上にできていると思うと有り難みもましてきます。 世の中のテキストエディタなどを作っているエンジニアの方々の苦労が忍ばれます。
この文章を書いている Vim のコントリビューターの方々には尊敬の念を禁じ得ません。

Unity 2017 では標準の Timeline がかなり便利なので、そちらの UI を手軽に Unity 5 系でも使えたら嬉しいなあと思います。

明日は9日目です。hohean さんの記事です。お楽しみに