KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

カテゴリ: Unity

はじめまして。普段はiOSを使っているのに開発中はAndroid周りについて見ること多いsuzu-jです。

最近はHuawei製の端末等、国内でも搭載しているSoCがSnapdragonではない端末が増えてきました。 そんな端末でも、読み込まれているテクスチャや、描画順の確認等、グラフィックス周りのデバッグをするために、今回は Mali Graphics Debugger の使い方を紹介します。

対象はCPUがARMでGPUがMaliのAndroidとなります。 x86系統のCPUだとrootedな端末が必要な上、手順が違うため今回の記事では対象外です。

説明は dtab Compact d-02HWindows 上で行いますので、Mac等その他のOSを使っている方は適宜読み替えてください。

なお、大体のアプリに繋げられてしまうSnapdragon系列と違い、こちらは残念ながら自分でビルドするアプリケーションでしか見れません。 むしろセキュリティ的に見えるのがおかしいと思うのですが。

PC側の準備

まずはダウンロード、公式サイトから自分のプラットフォームに応じてダウンロードしてインストールします。

インストール後は Edit->PreferencesPath to ADB にADBのパスを指定しておきましょう。

mgd_adb_path

端末側の準備

端末側にはDebuggerと繋げるためのアプリケーションをインストールする必要があります。

Windowsでインストール先を変えていなければ、 C:\Program Files\Arm\Mali Developer Tools\Mali Graphics Debugger v4.8.0\target\android\MGD.apk があるため adb install でインストールしましょう。

アプリケーション側の準備

アプリケーション側にはPluginを入れる必要があります。

Pluginは C:\Program Files\Arm\Mali Developer Tools\Mali Graphics Debugger v4.8.0\target\android\ 以下に、各CPUに応じたlibMGD.soがあるので、デバッグしたい端末に応じて配置していきましょう。

今回の端末はarm-v7aの端末なので C:\Program Files\Arm\Mali Developer Tools\Mali Graphics Debugger v4.8.0\target\android\arm\unrooted\armeabi-v7a\libMGD.soAssets\Plugins\Android\libs\armeabi-v7a 以下に配置します。

通常であればこれで準備完了となります。

Debuggerとアプリケーションの接続

対象アプリのAPKをインストール後に、PCとAndroidを繋げたまま、Android側の Mali Graphics Debugger を起動しましょう。 Enable MGD Deamon: の項目をONにして下のアプリケーション一覧からDebuggerに繋げたいアプリを起動しましょう。

mgd_android

アプリを起動したらPC側のDebuggerから Debug->Open the Device Manager を選択すると、接続している端末の一覧を開きます。

mgd_test_select_device

接続したい端末名の左にあるアイコンを選択すれば接続完了。晴れて各種描画関連の確認が出来るようになりました!

mgd_test_connected

上手く接続できない場合

Debugger起動前にアプリを開いている場合、うまく接続できないことがあるようなので、アプリのタスクキルをしてから再度開きなおしてみましょう。

Activityをカスタマイズしている等の場合、Activity側にも手を入れる必要があります。UnityPlayernew するより前に System.loadLibrary("MGD"); を呼んであげましょう。 よくわからないけれど使っている外部PluginがActivityを書き換えている、という場合は onCreate をOverrideしたActivityを作ってあげると動くことが多いと思います。

それではよい最適化ライフを!

明日、20日目はやまださんです。よろしくお願いします。

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

皆さんこんにちは、 jukey17 です

私はクライアントサイドエンジニアとしてゲームのプロトタイプを作る仕事を何度か経験しているのですが、今回はその際に体得した自分なりのノウハウをまとめてみようと思います よろしくお願いします

はじめに

この記事では、Unityを使ったプロトタイプ開発をベースに少人数体制(プログラマ・プランナー・デザイナー各1名ずつなど)で開発を進めていった経験を元に話を進めていきます

なのでUnityを使ったゲーム開発の経験がある方を対象とした内容になっています

プロトタイプ開発の仕事での経験ベースの話ではありますが、個人制作や学校・サークルでのチーム製作にも置き換えて考えられるような内容にまとめているので、参考にして頂ければと思います

仕様を素早く実装する → 仕様の変化を先読みした実装をする

最初に どういった考え方でプロトタイプ開発をすべきか というまとめっぽい話から始めます

大枠としてこれを念頭に置いて開発を進めておきたいということが伝わればと思います

プロトタイプ開発は「どのようなゲームを作っていくべきかを模索する」段階であるため、仕様がキッチリと決まっていることはほぼありません

アレを試してみよう、コレを組み合わせてみよう、やっぱりソレは削ろう... このようにして目まぐるしく実装内容が変わっていくのがプロトタイプ開発です

そうなってくるとついつい「とにかく数をこなすために急いで実装しなければ」と考えてしまうのですが、そうしてしまうと使い回しの効かない書き捨て品質なコードを書いてしまいがちになってしまいます

1回実装して終わりであればその実装の速度を重視して書き捨て品質にコードを書いてもよいと思うのですが、プロトタイプ開発では複数の種類の実装をしたり、その中で幾つかの機能を使い回してみたりと、とにかく色々な要素を組み合わせて面白さを追求していくため1回実装して終わりということは絶対にありません

  • 「先週試して外した機能をもう一度復活させたい」
  • 「パターンAで調整したパラメータをパターンBでも使いたい」
  • 「別の機能で使っている演出をとりあえず使い回してほしい」
  • などなど...

プロトタイプ開発では上記のような試行錯誤が沢山繰り返されます(特に後半になればなるほどこのような内容が増えます)

期間内で継続して開発スピードを出すにはこれらの試行錯誤を如何に予測して捌き続けることができるかが鍵だと個人的には思っています

ということで、上記の例を元にこれらの考え方を持ってどのように開発を進めていくのかを紹介していきたいと思います

プロトタイプ開発でありがちなケースとその対応

「先週試して外した機能をもう一度復活させたい」

仕様の定まっていないプロトタイプ開発ならではのよくあるケースの一つです

これをシューティングゲームにて敵を追尾していく弾を発射するという仕様を例に2つのコードを比較してみましょう

  • サンプルコードA
class Player : MonoBehaviour
{
    [SerializeField] GameObject homingBulletPrefab;
    [SerializeField] float homingBulletProgress;
    [SerializeField] float homingSpeed;
    [SerializeField] GameObject homingFinishEffectPrefab;
    
    GameObject homingBulletObject;

    void Update()
    {
        // 追尾弾発射
        if (Input.GetKeyDown(KeyCode.H))
        {
            // 連続では撃てない
            if (homingBulletObject == null)
            {
                homingBulletObject = Instantiate(homingBulletPrefab);
                homingBulletProgress = 0.0f;
            }
        }

        // 追尾弾の処理
        if (homingBulletObject != null && enemy != null)
        {
            homingBulletObject.transform.position = Vector3.Lerp(transform.position, enemy.transform.position, homingBulletProgress);
            homingBulletProgress += Time.deltaTime * homingSpeed;

            if (homingBulletProgress > 1.0f)
            {
                Destroy(homingBulletObject);
                homingBulletObject = null;

                // 着弾したら自機に演出が出る
                var effect = Instantiate(homingFinishEffectPrefab);
                Destroy(effect, 2.0f);
            }
        }
    }
}
  • サンプルコードB
class Player : ShipBase
{
    // 追尾弾の処理は特になし
}

[ReqiredComponent(typeof(ShipBase))]
class HomingBulletFirer : MonoBehaviour
{
    [SerialzieField] HomingBullet homingBullet;
    [SerializeField] float speed;

    ShipBase parentShip;

    void Start()
    {
        parentShip = gameObject.GetComponent<ShipBase>();
    }

    void Update()
    {
        // 追尾弾発射
        if (Input.GetKeyDown(KeyCode.H))
        {
            // 連続では撃てない
            if (!homingBullet.IsActive)
            {
                homingBullet.Fire(parentShip.transform, parentShip.Target.transform, speed, OnLanded);
            }
        }
    }

    void OnLanded()
    {
        // 着弾したら自機に演出が出る
        EffectManager.SpawnPlayerHomingFinishEffect();
    }
}

class HomingBullet : MonoBehaviour
{
    public void Fire(Tranform start, Transform end, float speed, Action onLanded)
    {
        // 弾を発射する処理
    }
}

サンプルコードAでは追尾弾をそのまま自機(Player)クラスに書いていますが、サンプルコードBでは追尾弾を発射するクラスと追尾弾自身のクラスを分けて実装しています

サンプルコードAの場合は表題の対応をする場合は、該当コードをコメントアウトしておいてそれを解除するか、削除しておいてgitなどのバージョン管理システムから掘り起こして復活させなければなりません

サンプルコードBの場合は HomingBulletFirer クラスを Player クラスが付いている GameObject に付けたり外したりするだけで対応が可能です

今回のようなシンプルな実装内容であれば、サンプルコードAのような実装でも一括でコメントアウトするだけでなんとかなりそうですが、もっと処理が複雑になって色んな所に該当コードが散らばっていくとそう簡単にはいかなくなります

サンプルコードBのように 「機能単位でパーツ(部品)を分けておく」 ことで繰り返される試行錯誤にも素早く対応していくことが可能です

Unityではこのようなまとまった処理をパーツ(部品)として扱うコンポーネント指向を基本とした設計がなされているので、サンプルコードBのようなパーツを分けた実装がしやすい構造になっています

実装を進めながらドンドンとパーツを増やしていき、それらを付けたり・外したり・組み合わせたりするだけで素早く沢山のパターンを試せるような状況を作っていくようにしましょう!

「パターンAで調整したパラメータをパターンBでも使いたい」

プロトタイプ開発では複数パターンを用意して遊び比べることが多くこういった話もよくあるできごとです

こちらもシューティングゲームを題材に2つのサンプルコードでどのように対応すべきかを見ていきましょう

サンプルコードA

// 撃てる弾の種類が複数ある・ボムが使える自機
class PlayerA : MonoBehaviour
{
    [SerializeField] int baseHp;
    [SerializeField] int baseAttack;
    [SerializeField] float moveSpeed;
    [SerializeField] BulletType[] usableBulletTypes;
    [SerializeField] int maxBombCount;

    ...
}

// 撃てる弾は1種類で特殊なスキルが発動できる自機
class PlayerB : MonoBehaviour
{
    [SerializeField] int baseHp;
    [SerializeField] int baseAttack;
    [SerializeField] float moveSpeed;
    [SerializeField] float skillIntervalTime;

    ...
}

サンプルコードB

class PlayerParam : ScriptableObejct
{
    [SerializeField] int baseHp;
    [SerializeField] int baseAttack;
    [SerializeField] float moveSpeed;
}

// 撃てる弾の種類が複数ある・ボムが使える自機
class PlayerA : MonoBehaviour
{
    public void Initialize(PlayerParam initialParam, BulletType[] usableBulletTypes, int maxBombCount)
    {
        // パラメータの保持などの初期化処理
    }

    ...
}

// 撃てる弾は1種類で特殊なスキルが発動できる自機
class PlayerB : MonoBehaviour
{
    public void Initialize(PlayerParam initialParam, float skillIntervalTime)
    {
        // パラメータの保持などの初期化処理
    }

    ...
}

サンプルコードAでは2パターンの自機をクラスに必要なパラメータを SerializeField 属性を付けて定義していますが、サンプルコードBではパラメータクラスを用意して初期化処理を呼んだ際にそれを設定しています

どちらのコードも外からパラメータを設定できるようにしている点は同じですが、サンプルコードBではパラメータを専用のクラスに逃している点に注目してください

このようにロジック(自機の挙動を定義する Player) とデータ(自機のパラメータを定義する PlayerParam)に分けることでパラメータの使い回しが容易になります

今回は自機を対象としていますが、シューティングゲームであれば、敵や弾、ボムなどある程度の機能単位でデータを別クラスに逃しておくのをオススメします

このようにして先にデータを切り出しておくことで、ある程度内容が固まったあとにプランナーの人を中心にパラメータ調整を行うといったことになってもスムーズに移行することができます

Unityの場合、データとして扱うために最適化された ScriptableObject やJSONを扱うための JsonUtility などがあるのでそれを活用しましょう!

Tips パラメータを外部から設定できるようにするためには何を使うべきか?

SerializeField 属性(Inspector)を使う

一番手っ取り早いのはサンプルコードAでも紹介している SerializeField 属性を使ってInspectorから設定できるようにする対応です

これは複数パターンを試したり色んなところで使い回したりしないようなパラメータ(ゲーム全体の設定など)を弄れるようにしたい場合に使いましょう

ScriptableObject を使う

SerializeField を使う場合に比べて実装コストは少し上がりますが、サンプルコードBのように複数パターンを試したり色んなところで使い回したりするような場合に効果を発揮できます

一度構造を作ってしまえばデータを複製してパターンも作れますし、そのままAssetBundle化することも可能です

のちのちエンジニアの手を使わずにデータのチューニングをしていくことを見越しておくと先にデータを外部ファイルに逃がしておいたほうがよいです

JSONを使う

利点は ScriptableObject と同じです

最終的にそのデータをどのように取得してくるかを想定して使い分けるとよいと思います

APIを叩いてレスポンスを受け取ってから次の処理に進むようなケースが想定されている場合はデータをJSONで定義しておいたほうが都合が良いでしょう(ステージの難易度情報やデッキの情報など)

「別の機能で使っている演出をとりあえず使い回してほしい」

仮で用意しておき、後から違うものに入れ替えるという状況もプロトタイプ開発ではよくあるケースです

プロトタイプ開発ではゲームの中身をどれだけ面白くできるかに注力して進めていくので、見た目の作り込みが後追いになることが多いです

進め方によってはデザイナーは関与せずにプリミティブなオブジェクト(簡単な記号や図形)のみを組み合わせただけでプロトタイプ開発を行うこともあります

そういった場合でも手戻り少なく実装を進めるにはどうするべきか、こちらもコードを見てみましょう

サンプルコードA

class Player : MonoBehaviour
{
    // HPを回復したときに呼ばれる
    void OnHeal()
    {
        // 回復演出は出しっぱなしにしてそのまま消す
        var effect = Instantiate(healEffectPrefab, transform);
        Destroy(effect, 1.5f);
    }

    // HPがゼロになったときに呼ばれる
    void OnDead()
    {
        if (isDead)
        {
            return;
        }
        StartCoroutine(PlayDeadEffectCoroutine);
    }

    IEnumerator PlayDeadEffectCoroutine()
    {
        var effect = Instantiate(deadEffectPrefab, transform);
        yield return new WaitForSecond(2.0f);

        Destroy(effect);
        // ゲームオーバー処理
        GameOver();
    }
}

サンプルコードB

class Player : MonoBehaviour
{
    // HPがゼロになって死亡演出が完了した後に呼ばれる
    public Action OnFinishDeadEffect { get; set; }

    // HPを回復したときに呼ばれる
    void OnHeal()
    {
        EffectManager.EmitHealEffect();
    }

    // HPがゼロになったときに呼ばれる
    void OnDead()
    {
        EffectManager.EmitDeadEffect(OnFinishDeadEffect);
    }
}

サンプルコードAでは Player クラスに直接エフェクトの発生処理を書いているのに対し、サンプルコードBでは エフェクトの発生処理を EffectManager にお願いする形になっています

自機のための演出になるので Player クラスに処理を書くと言う考え方は間違ってはいませんが、ゲームでの演出処理は「演出が終わったら」や「途中で中断したい」などの複雑な対応をするケースが多いです(そして調整の度にそのタイミングが代わります)

Prefabなどにまとめて全て演出側に処理を逃がせれば理想的ですが、内容がコロコロ入れ替わるプロト開発ではいきなり全ての処理を外に逃がすのも難しいです

そこでサンプルコードBでは EffectManager なる演出の複雑な遷移などをアレコレ吸収してくれる機能を外に用意しました

このようにしておくことで演出の差し替えが後で入ったとしても基本的には EffectManager に手を入れるだけにすることができます

ただし EffectManager をそのままエフェクトを何でも処理してくれる便利屋として扱っていくと今度は EffectManager が肥大化していって整理が難しくなってきてしまうので、その場合は様子を見つつ EffectManager も分割していきましょう

まとめ

ということでここまでつらつらと書き連ねて来ましたが、内容的にはプログラミングを設計する上で気をつけた方がよい基本的な事柄をまとめているということに気づきましたでしょうか

要は プロトタイプ開発であろうと速度重視の書き捨てコードを書くべきではない というのが私の結論です

少人数(1人)で開発を進めていくとついついルーズにコードを書いてしまいがちですが、開発が進んでいけば自分以外の人も多く関わってくることを考えると常に整理しながら開発を進めていけるのが理想です

個人の開発だったとしても、何日か経ったあと自分の書いたコードを見返して何が書いてあるのかよく分からなくなると言う経験はしたことがあるはずです

未来のチームメンバー(自分)が少しでもラクにできるコードを書くように心がけていきましょう!

明日の19日目はsuzu-jさんです。よろしくお願いします。

初めに

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

こんにちは、クライアントエンジニアの norm81 です。

初登場です、よろしくお願いします。  

経緯  

Unityでナビメッシュのベイク情報をプレハブにしたい、そしてアセットバンドルで運用したいという要望がありました。

既存のA*アセットなどを検証していたところ、Unity5.6からNavMeshBuilderが強化されたのでそちらで実現可能か検証しました。 

静的にベイクしたデータを用いたサイトをあまり見掛けなかったので今回纏めてみようと思います。  

検証に用いたUnityバージョンは5.6.3f1です。

結論

いきなり結論になりますが、上記の要望は対応できました。  

但し、制限があります。  

今回の方法において注意点も見つかりましたので、併せて下記に記載しています。  

導入

下記をクローンし、対象のプロジェクトに入れます。

https://github.com/Unity-Technologies/NavMeshComponents/

Assets/NavMeshComponents/フォルダをコピーしてねと書かれてますが、  

今回はAssets/Examples/フォルダも用います。  

手順

基本的には既存にあるナビメッシュの作成の通り、設定を行なっていきます。  

但し、Bakeは別途行うのでこのタイミングでは行いません。  

  

ヒエラルキーでナビメッシュの対象にするRendererコンポーネントを持つオブジェクトの親に、一つ空オブジェクトを作成します。  

そしてAssets/Examples/フォルダに入っているNavMeshPrefabInstanceコンポーネントをAddComponentします。  

01

そのオブジェクトを含むプレハブを保存した後にインスペクタのSelectボタンを押す、或いはプロジェクトウィンドウから保存したプレハブを選択すると、入れ子にあるオブジェクトが表示されます。

そのままNavMeshPrefabInstanceコンポーネントを持つオブジェクトを選択して、インスペクタのNavMeshPrefabInstanceコンポーネントのBakeボタンを押します。  

するとプレハブにNavMeshが追加されます。

確認 

Navigationウィンドウを開き、Show NavMeshを有効にした状態でWalkableのメッシュが表示されていれば成功です。

02

表示されていない場合、追加されたNavMeshを確認してみてください。Source BoundsのExtents値が(0, 0, 0)だと失敗している可能性が高いです。

03

下記の注意点を確認いただき、再度上記のBakeを試みてください。

成功した後は、このプレハブをアセットバンドルにすればそのまま運用可能です。

具体的な挙動については、上記NavMeshComponentsのソースコードを参考されることをオススメします。

注意点

NotWalkableを反映したナビメッシュがBakeできない。

従来のNavigationウィンドウでのBakeとは異なり、差集合のベイクがなされないです。  

対案としてMeshColliderを併用して目的座標に対して上空からのRayCastで判定を行い進入可能か調べる様なような二段構えの対応を検討しています。

FBXなどModelImporterを介するMeshを扱う場合、Bake前にRead/Write Enableを有効にする必要がある。  

04

プレハブに保存した後に無効に戻すのは問題なかったです。  

まとめ

現時点の外部データ化するメリットは、

  • ランタイムでベイクしなくて良いので処理コストが軽減できる。  
  • 1つの背景データと複数の外部データ化したナビメッシュを組み合わせることで、クエスト進行度などに併せてナビメッシュを切り替える運用ができる。  

といったところで、デメリットは注意点で記載したとおりになります。  

今回紹介した機能は折角の標準搭載された機能なので使い倒してみたいと思います。

以上

この記事は 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 さんの記事です。お楽しみに

(本稿はKLab Advent Calendar 2017 の1日目の記事になります)

2017年のKLabのアドベントカレンダーです。最初はoho-sです。よろしくお願いします。

自分が作ったVRコンテンツをどんなのか説明したい!

VRコンテンツを作っていて、自分が作ったVRコンテンツをどんなものか説明したり共有したい時ってありませんか? ハイスペックPCやVR用HMDを持っていない人にも、雰囲気だけでも伝えたい。そんな時に便利なのが、いわゆる360°動画でのキャプチャです。 今回は、Facebookが公開している、360-Capture-SDKを使ってキャプチャしてみます。日本語での使い方の情報が探してもあまりなかったので、参考になれば幸いです。

Facebookの360-Capture-SDKを使ってみる

360-Capture-SDKの動作環境は、こちらを参考にしてください。 Windows(64bitのみ)の8以降で、NVIDIAもしくは、AMDのGPUが必要になります。各ドライバのバージョン指定があるので確認してください。 対応しているUnityのバージョンは、README.mdに記載がありませんが、今回Unity 5.5.3p3 (64-bit)で動きました。一方、Unity 2017.2.0p2 (64-bit)では、動画のキャプチャはできましたが、画面の表示がされないなどの不具合が見られました。

さて、では使ってみましょう。まず、キャプチャしたいUnityプロジェクトにSDKを組み込みます。 360-Capture-SDKが公開されているリポジトリは、こちらです。 このリポジトリをcloneしてきて、 Samples/Unity/Assets 以下のEncodePackageフォルダとPluginsフォルダをそのまま組み込みたいプロジェクトのAssets以下にコピーします。

次に、EncodePackage内の、EncoderObjectプレファブをシーンに置きます。

組み込み

そして、EncoderObjectのインスペクターからいくつかの録画用パラメータなどを修正などします。 負荷とクオリティを考えて、ピクセル数などを選びましょう。 EncoderObjectの位置がキャプチャの中心位置になるので、適切にPositionを設定します。少し離れた位置にすると、観客視点とかもできると思います。

最後にキャプチャをします。 プロジェクトを実行して、キャプチャしたいポイントでF2キー(デフォルトでは。インスペクターで変更可能)を押すとキャプチャ開始、F3キーで停止です。キャプチャした動画は、プロジェクトフォルダのGallery内に保存されます。

動画のメタデータの調整

ここまでの手順で作成した動画を、そのままYouTubeやFacebookに投稿しても360°動画再生の専用プレイヤーになりません。 動画に360°動画であることのメタデータを付けてやります。

そのためのツールが、こちらになります。

展開して実行すると下図のようなダイアログが出てくるので、「Open」からキャプチャした動画ファイルを選び、「Spherical」 のチェックボックスをオンにして「Inject metadata」します。以上で新しい動画ファイルができたと思います。

ダイアログ

アップロードしてみる

通常の手段でアップロードします。FacebookやYouTubeであれば、自動的に先ほどの手順でつけたメタデータに基づいて処理してくれて、360°動画として扱われるはずです。

サンプル動画ファイルリンク

まとめ

以上のように、比較的容易に360°動画でのキャプチャができてしまいました。 VRコンテンツのプロモーションなどに使えるのではないかと思います。 是非試してみてください。

最後に余談ですが、EncoderObjectのインスペクターを見ていると、どうもキャプチャーした動画のストリーミングができるようですが、試してみたところうまく動きませんでした。 この辺りも面白そうなので、今後調査を進めてみたいと思います。

KLab Advent Calendar 2017 の2日目は、haltさんです。よろしくお願いします。

↑このページのトップヘ