Programming Serendipity

プログラミングを中心に種々雑多に書き留めます

uGUIのScrollViewを使いこなす7つのTips

この記事は Unity Advent Calendar 2016 3日目の記事です

Unityは2つとも満員で、さすがに盛り上がってますねー。自分は普段縁がないようなWeb系やサーバーサイドのアドカレをぼんやり眺めるのが好きです。同じプログラミングでもかなり世界が違うものだなと感嘆します。

さてさて、Unity5.2で追加されたuGUIのScrollView。様々な手順が簡略化され、実用的になっていますが、そのままでは若干痒い所に手が届かないところがあります。ここでは、それらを解決する案を紹介しようと思います。

基本的な使い方

その前に、まずは簡単な使い方をおさらいしましょう。
Create > UI > ScrollViewを選択します。
すると f:id:q7z:20161130222505p:plain
このようなものが生成されます。

ここでは縦のスクロールビューを作ることにしましょう。
その場合、ContentにVertical Layout GroupContent Size Fitterをアタッチし、Content Size FitterVertical FitPreferred Sizeにします。
そして、ButtonなどのアイテムをContentの子供に配置し、Layout ElementをアタッチしてMin Heightに適当な値を入れ、好きなだけ複製すると動くようになります。これ以前に比べるとだいぶ簡単になりました。
横スクロールの場合は、Horizontal Layout Group、1行に複数のアイテムを持たせたい場合はGrid Layout Groupにします。

それでは、Tipsをご覧いただきます。

1. アイテムはプレハブ化する

Content以下に配置するアイテムが似ている場合はプレハブ化しましょう。 こうすることで、同じ変更を簡単にすべてのアイテムに反映させることができます。

2. アイテムの子供への参照は持たず、それぞれのアイテムが持ちそのアイテムへの参照を持つ

スクリプト操作の話です。 大本のスクリプトからアイテムの子供のそれぞれのオブジェクトを直接参照するよりも、アイテムの親に別のスクリプトをつけてそこへの参照を持ちましょう。

f:id:q7z:20161130224228p:plain

そしてそのスクリプトから各々の子供のオブジェクトを参照することで、アイテムが増減しても変更箇所が1か所で済みます。
スクリプトはこんな感じになるでしょうか。

        // Item.cs側
        public void SetInformation(string header, string title, string duration)
        {
            this.header.text = header;
            this.title.text = title;
            this.duration.text = duration;
        }
        
        // ItemManager.cs側
        void Start () 
        {
            for (int i = 0; i < items.Count; i++)
            {
                items[i].SetInformation("", "", "");
            }
        }

これはScrollViewに限らずUnity全般同じことが言えます。

3. スクロールバーを消したい場合、ScrollRectのスクロールバーを削除する

縦横どちらかにしかスクロールしない場合、もう片方のスクロールバーは不要になることがほとんどです。Scroll RectのスクロールバーのVisiblityAuto Hide系を設定していれば実行時に自動的に消えてくれますが、編集時にもいらないという場合、Scroll Rectコンポーネント内のHorizontal Scrollbarを選択してShift+Delで削除してしまいましょう。実体のほうも消して大丈夫です。

ちなみに、標準のScrollbarでもwidthを10、色を黒、透明度を0.7くらいにすると素材を変えなくてもそこそこ見栄えがいい感じです。

4. スクロールビューのクリップ範囲を変えたいときの方策

ScrollViewはそのままだとImage目一杯の範囲でクリップされてしまうので、例えば背景画像のふちに装飾がある場合など、端っこより前でクリップしたいことが多くあります。 f:id:q7z:20161130224315p:plain これに対する解決策は2つ考えられます。 まず、ViewPortのScaleを0.95くらいにする方法。 f:id:q7z:20161130224325p:plain 手軽ですが、中のオブジェクトもすべてそのスケールになるので場合によっては注意が必要です。 もう一つは、背景画像とScrollViewを分割し、親子関係にする方法。こちらのほうが柔軟性は高いかもしれません。 f:id:q7z:20161130224332p:plain

5. 上下を残したい場合、LayoutGroupのTopやBottomの数値を上げる

↑とも関連がありますが、背景画像に装飾がある場合など、端までスクロールさせたときに最後のアイテムのスクロールの限界を中央寄りにしたい時があります。 その場合、Vertical Layout GroupPaddingの項目を開き、Bottomに20などの値を入れてみてください。そうすると、その数値分だけ領域が広がり、余分にスクロールさせることができます。 Horizontal Layout GroupGrid Layout Groupでも同じ設定があります。 f:id:q7z:20161130224341p:plain

6. スクロールの両端をフェードさせる方法

さて、ここまででクリッピングも正常になり余白も残せましたが、切れ目がはっきり出ているのが気になるかもしれません。そこで、 f:id:q7z:20161130232952p:plain このようにする方法を紹介します。 やり方としては、上にシェーダーでフェードさせた背景と同じ画像を2回描画します。 f:id:q7z:20161130224811p:plain いろいろやり方を探りましたが、これが一番簡単で汎用性が高い気がします。(Raycast Targetを外すのを忘れずに)
fragmentシェーダーの部分だけ書くとこんな感じです。

fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
c.a *= lerp(0, 1, (IN.texcoord.y - _Start) / (_End - _Start));
return c;

いたってシンプル。
一応、それぞれ別のマテリアルになり、ドローコールを3使う方法なのでモバイルでは注意。

7. スクリプトからなめらかにスクロールさせる

scrollRect.verticalNormalizedPositionがスクロールの位置になっているので、この数値をいじることでスクリプトからスクロールができます。 ただし、この数値は下端が0、上端が1になっているので注意。 さらに、Mathf.Lerp()を使うと滑らかに動かせるでしょう。

// 例えばこんな風に
void Update () 
{
    if (scrollRect.verticalNormalizedPosition != targetPosition)
    {
        scrollRect.verticalNormalizedPosition = Mathf.Lerp(scrollRect.verticalNormalizedPosition, targetPosition, 0.16f);
    }
}

まとめ

ここまでを実装したものをGithubにあげました。

github.com

スクロールの可否を動的に決定するDynamicScrollable.csもおまけで付けてあります。

というわけで、簡単に実装できるScrollViewも工夫すればさらに実用性が高まります。
慣性でスクロール中に端に到達したとき、バウンスバックせずにピタッと止まってしまうのが難点ですが、それ以外はいい感じです。 NGUIではバウンスバックするので、より高度なUIを作るときはいまでもNGUIが人気の理由でしょうか。 逆に言うとそれほど凝らなければuGUIで十分ということでもあります。

あなたは、ScrollViewをどのようにつかっていますか? 何か面白いアイデアがあれば教えてください。( ´∀`)

Have a nice ScrollView!

4日目はhecomiさんです。よろしくお願いします。

参考リンク

UnityのuGUIでスクロールビューを作る (Unity4.6~5.1で必要だった方法)
UnityのuGUIで無限にスクロール出来るスクロールビューを作る

最後に宣伝

ほかにもいくつかUnityネタがあるのですが、Unityが満員なのでさいたまげーむす Advent Calendar 2016に投稿していきます。