2018年6月22日金曜日

UnityのGameViewのScaleを1に強制的に戻すエディタ拡張

UnityのGame Viewが勝手に拡大されるというMASAさんのツイートを見ました


そう言われてみると、思い返せば確かにプレイ時に手動でスケールを戻すことをよくしてる気がします。



非プレイ時にマウスのセンターホイールをスクロールするとここのスケールが変わるので、多分プレイボタンを押す際に間違ってセンターホイールに触れてるんじゃないかと推測してます。

プレイ時にスケールが拡大されてて戸惑うことはあってもうれしいことはほぼありません。
そこで、プレイ時に強制的にスケールを1に戻すエディタ拡張を書くことにしました。


UnityEditor.GameViewは公開されてないので、リフレクションを使ってアクセスします。
 
                var asm = typeof(Editor).Assembly;
                var type = asm.GetType("UnityEditor.GameView");
                EditorWindow gameView = EditorWindow.GetWindow(type);

というコードで、GameViewのインスタンスにアクセスできます。

問題は、GameViewのどの内部関数を呼べばスケールを1に出来るかです。

それを調べるために、GameViewのソースを見てみましょう
 https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/GameView/GameView.cs

Scaleに関することなので適当にScaleで文字列検索してソースを読むと、SnapZoomの引数を1で呼べばスケールが1に戻りそうな気がします。

試してみましょう。

#if UNITY_EDITOR

using UnityEditor;

static class GameViewUtil
{   
[InitializeOnLoadMethod]
    static void CheckPlaymodeState()
    {
        // プレイモードが変わったときのコールバックに登録する
        EditorApplication.playModeStateChanged += x =>
        {
            // Playモードに変わったときに処理する
            if (x == PlayModeStateChange.EnteredPlayMode)
            {
                var asm = typeof(Editor).Assembly;
                var type = asm.GetType("UnityEditor.GameView");
                EditorWindow gameView = EditorWindow.GetWindow(type);

                // GameViewクラスのSnapZoomプライベートインスタンスメソッドを引数1で呼び出す
                var flag = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
                type.GetMethod("SnapZoom", flag, null, new System.Type[] { typeof(float) }, null).Invoke(gameView, new object[] { 1 });
            }
        };
    }
}

#endif
このコードをファイルに保存し、どこでもいいので配置します。
(Editorフォルダの中に入れるならば、#ifdef UNITY_EDITORは必要ありません)

そして実行してみると、Unity 2018.1では無事実行するたびにスケールが1に戻ることがわかります。

ただし、リフレクションで公開されてない内部関数にアクセスしてるため、バージョンが違うと動作しなくなることも十分ありえます。

おわり

2018年6月18日月曜日

Unity1week GameJamに参加しました


最近Unity 1週間ゲームジャムに参加しました

完成した作品がこちらです。



ゲームのネタについて

何かを薄く切っていくゲームを考えていたところ
ちょうどこちらのバズっていたとても面白いツイートを見て、題材はパンの薄切りゲームにすることにしました。

使用したアセットについて

Mesh Cut

このゲームで一番大事なメッシュを切り刻むことができるアセットです。
アセットストアにあるものをいくつか見て回ったのですが、どれも$30以上しました。
$30するアセットは、その値段に見合う豊富な機能を持ってることが多く、使い方の学習に多くの時間を取られがちです。
今回はこちらのMIT Licenseのコードを使わせていただくことにしました。
ソースが公開されてるおかげで、改造も可能で助かりました。

Realistic Bread

パンです。$2.69でナイフとまな板がついてきます。

ファンシーハートフォント

とてもかわいいフォントです。これをTextMeshProで使ってます。

効果音

かわせるかしら?などの掛け声も効果音ラボサイトのアセットを使わせていただきました。

音楽

タイトル画面とゲームオーバー時の音楽です。

実装について

パンを切る部分を光らせた


どこがどう切れるのか明示しないと納得感が薄かったので、パンに切られラインを表示しました。
このラインはパンのシェーダーに「シェーダーパラメーターで与えた面との距離を調べて、近いほど光らせる」という処理を入れて実現してます。

もっといい方法はあるはずですが、自分が一番手っ取り早くできそうな方法でやってます。
ゲームジャムですからね。

背景はスカイボックス

パン包丁まな板以外は普通にUnity標準の青空だったのですが、クッキング感を出すために背景をチッキンにすることにしました。
ただ、キッチンのモデルを本当に配置したら、容量、実行速度などに大きな影響がでます。
そこで、テラシュールブログさんの「背景をスカイボックスで表示する」と同じ手法を使いました。

別プロジェクトにキッチンモデルアセットをインポートし、そこでライトを焼き、キューブマップを撮影し、それをパン切りプロジェクトに移行してます。

キューブマップのサイズを256ぐらいにすると、なんか被写界深度っぽくなる上、サイズも大幅削減できました。



容量12MBまで削減

WebGLで実行するものは、やはり容量が少なければ少ないほどサーバーにもユーザーにも優しいはずです。

何が容量を占めているのか、勘で闇雲に作業してはコスパが悪いです。
そこで役に立つのがビルド後レポートツールです。
アセットストアにはそれ系のツールはいくつかありますが、自分はこれを使用してます。

Better Build Info - Report Tool



何が容量を占めているのか?を一覧で見たり、2次元の面積図で見れたりと、一気にわかりやすくなります。

自分のプロジェクトで大きかったファイルは「日中韓すべての文字が入ったフォント」
「パンモデルなどのテクスチャ」が多かったです。

フォントは、漢字などが出ないのはもう切り捨てて、「ふぁんしーはーとフォント」で全部表示するようにし、大きいフォントファイルは削除しました。
ランキングで登録する人も、アルファベットかひらがなカタカナ登録が多いようでしたので。
ゲームジャム作品ですし、ランキングで漢字が使えることより、容量が軽いほうがいいでしょう。

テクスチャは、問題がないレベルまで解像度を下げたり、無駄にアルファを使う設定になってるものはアルファを使わないように設定し直すなどしました。
無駄にアルファを使う設定になってると、圧縮フォーマットの関係でサイズが大きくなることがあります。

なぜ?実際にブラウザで実行したらメモリ不足で落ちちゃう

Editorでは落ちないのですが、クローム上で実際に動かすとパンを数枚切ったところで落ちたり落ちなかったりしてました。

Build時に使用メモリを指定することができるので、それを増やせばいい
というような安易な解決はせず、ちゃんと原因を探りました。

容量12MBまで削減して、メモリにのるアセットも少ないはずなのになぜ落ちるのか?
勘で調べるのは大変なので、もちろんUnity備え付けのプロファイラを使いました。

プロファイラをつけて実行してみると、パンを切った瞬間に60MB以上アロケートされ、即座に開放されてました。

Deep Profileをonにして実行すると、どの関数でメモリを消費してるのかもわかります。
ずばり、MeshCut.Cut関数の中でした。

ソースを読むと

_leftSide.AddTriangle(
 new Vector3[]{ _victim_mesh.vertices[p1], _victim_mesh.vertices[p2], _victim_mesh.vertices[p3] },
 new Vector3[]{ _victim_mesh.normals[p1], _victim_mesh.normals[p2], _victim_mesh.normals[p3] },
 new Vector2[]{ _victim_mesh.uv[p1], _victim_mesh.uv[p2], _victim_mesh.uv[p3] },
 new Vector4[]{ _victim_mesh.tangents[p1], _victim_mesh.tangents[p2], _victim_mesh.tangents[p3] },
 sub);

というコードがありました。

_victim_meshはUnityのMeshクラスです。
Meshクラスのverticesプロパティは、その見た目の印象と違い、内部の配列に直接アクセスしてるわけではなく、コピーした配列を作って返します。
そのため、「_victim_mesh.vertices[p1], _victim_mesh.vertices[p2], _victim_mesh.vertices[p3]」の部分だけでも3回配列がコピーされて即座に捨てられるということになっています。

悪いことにこれがfor文の中にあったので、メッシュ自体は数キロバイトでも、そのコピーが1フレーム中に1000回近く生成されていました。

そこで
            var mesh_vertices = _victim_mesh.vertices;
            var mesh_normals = _victim_mesh.normals;
            var mesh_uv = _victim_mesh.uv;
            var mesh_tangents = _victim_mesh.tangents;
というように、for文の外で変数に配列を代入して、mesh_verticesを使うよう置き換えました。
これだけでほとんど切った際にメモリを使わなくなりました。
MeshCutがオープンソースでありがたいところでした。

Unityのプロファイラはとても簡単で便利なので、それもとても助かりました。

最後に

今回は結構苦労することもなくサックり作れたのが良かったです。
こういうイベントに参加するのはいろいろと楽しいので、都合がよかったらまた出てみたいと思います。