この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
17新卒エンジニアがUnity関係で学んだことの備忘録を書きます。
はじめに
前回の記事では、Unityでシーンを作成し、キャンバス上に同じ形のボタンをVerticalLayoutGroupというコンポーネント(機能)を用いて自動配置で並べるところまで実装できました。
しかし、VerticalLayoutGroupで並べたボタンが画面外にはみ出してしまっています。
(前回の記事はこちら→UnityでゲームのUI作りたい! 〜 物並べ編①)
今回は、画像のようにはみ出してしまったボタン群を、スクロールしてクリック出来るようにしてみましょう。
準備
スクロールするUIを作るためには、大きく分けて
・スクロール対象となる領域
・対象を表示する領域
の二つが必要になります。
例として新幹線の窓から外の風景を眺める状況を挙げると、外の流れていく風景が「スクロール対象となる領域」にあたり、新幹線の窓が「対象を表示する領域」にあたります。
(この場合、実際に動いてるのはスクロール対象の風景じゃなくて新幹線ですが)
前回の時点で、スクロール対象となる領域の作成とボタンの配置が完了しているため、今回はまず対象を表示する領域を作成します。
さて、唐突ですが水色の部分が表示領域となります。
水色の領域の外にあるボタンは、たとえキャンバス上に存在していてもスクロールするまで見えないようにする必要があります。
その機能を追加する前に、この水色の表示領域の作り方をご紹介します。
前回はCanvas直下にScrollAreaというスクロール領域を作成しましたが、その間に表示領域となるオブジェクトを作成します。(本記事ではViewAreaとします)
親子関係の間に新たにオブジェクトを挟み込むため、まずCanvas直下にCreateでViewAreaを作成し、ScrollAreaをドラッグしてViewAreaの子オブジェクトにします。
ViewAreaを左側に寄せたかったので、PosXを-240にしています。
(前回は-160でしたが、さらに左側に調整しました。)
これで表示領域自体は作成できましたが、表示領域の外側にあるボタンが見えてしまっているので、自分の領域内だけ見えるようにする必要があります。
これを実現するためのコンポーネントが、 Mask です。
厳密には、マスク処理を行うコンポーネントは Mask と RectMask2D の2種類があります。
Mask
Maskコンポーネントを持っている親オブジェクトの子オブジェクトの形を、 親オブジェクトの形に制限する。 子オブジェクトが親オブジェクトからはみ出す場合、はみ出した部分が表示されなくなる。 マスクの形はImageコンポーネントに依存するため、画像次第でどんな形のマスクも作成可能。 (※画像に透過箇所がある場合、アンチエイリアスが効かないため境目が汚くなってしまいます。 綺麗な透過グラデーションを作る場合は、Mask機能を自前で実装する必要があります。)
RectMask2D
機能そのものはMaskコンポーネントと同じだが、 形が四角形(RectTransform依存)に制限される。 ただし、描画コストが少ないため、パフォーマンスはMaskより良い。
今回の表示領域は四角形なので、無駄の少ない RectMask2D を使用します。
RectMask2Dは、AddComponentするだけで自動的にそのオブジェクトの RectTransform を参照してマスク処理を行ってくれます。
さて、マスク処理を適用し、表示領域内のみボタンが見えるようになりました。
これでスクロール領域を作る準備が出来ました。
この勢いでスクロール機能を追加していきましょう。
スクロールの実装
早速ですが、スクロール機能は ScrollRect というコンポーネントで実現出来ます。
このScrollRectを、スクロールの対象となる領域(=本記事ではScrollArea)にAddします。
設定項目がたくさんありますね。一つ一つ見ていきましょう。
Content
スクロール対象オブジェクトのRectTransformをアタッチします。
Horizontal 、 Vertical
スクロールの方向を水平か垂直か決められます。
両方にチェックを入れると上下左右にスクロール可能になります。
MovementType
・Unrestricted
無制限にスクロール出来ます。
・Elastic
マウス等のドラッグによりスクロール領域の端が表示領域の内側まで
引っ張られた場合、ドラッグをやめることで表示領域の端と
スクロール領域の端が一致するように、引っ張られたゴムが
元に戻るような動作で自動的に戻ります。
・Clamped
スクロールに制限を付け、スクロール領域の端が
表示領域の内側に来ることがなくなります。
・Elasticity
Elasticを選択した場合のみ設定可能。戻る際の弾力を設定します。
Inertia
慣性の有無です。慣性がある場合、スクロールをやめても少しだけ
慣性によってスクロール領域が動きます。
・DecelerationRate
慣性の強さを0〜1の値で設定します。
ScrollSensitivity
マウスのホイールや指によるドラッグに対する感度を設定します。
Viewport
表示領域オブジェクトのRectTransformをアタッチします。
ここでアタッチしたオブジェクトに基づいて、
MovementTypeのElastic、Clampedが動作します。
HorizontalScrollbar 、 VerticalScrollbar
スクロールバーがある場合、水平・垂直それぞれここにアタッチします。
OnValueChanged (Vector2)
スクロールが行われ、位置が変わったときに呼び出されるイベントを設定できます。
これらの設定を、本記事の実装に合わせて行ったものがこちらになります。
スクロール対象領域をScrollAreaに、表示領域をViewAreaにそれぞれ設定し、
垂直方向のみスクロールさせたいのでHorizontalのチェックを外しています。
また、Elasticとの選択は好みの問題な気がしますが、MovementTypeをClampedにすることで見せる必要の無い部分(スクロール領域の外側) を見られないようにしました。
スクロールバー以外の設定が出来たので、とりあえず実行して動作を確認してみましょう。
ボタンが全て同じなのでピンと来ませんが、いい感じにスクロール出来ていますね。
MovementType:Clampedにより、必要以上にスクロールさせてもスクロール領域の端までしかスクロールしないようになっています。
スクロールバーの追加
次は、無くてもいいのですがあった方がスクロール領域っぽさが出るため、スクロールバーを追加します。
スクロールバーを実装するためのコンポーネントはScrollbarです。そのまんまですね。
スクロールバーはScrollbarコンポーネントとImageコンポーネントを自力で組み合わせて作成することも出来ますが、実はUnityのUI機能にそれらが最初から準備されています。
ViewAreaの上で右クリックし、UI -> Scrollbar を選ぶと、ScrollAreaと同じ階層にScrollbarが作成されます。
Scrollbarの子オブジェクトにはSliding Areaがあり、さらにその子にHandleがあります。
どれもその名の通りで、Sliding Areaはスクロールバーの可動領域、Handleはバーの掴む部分です。
Scrollbarコンポーネントにもたくさんの設定事項があります。
案の定、一つ一つ見ていきます。
Interactable
スクロールバーに対して入力を受け付けるかどうかの設定です。
Inspectorで設定することはほとんど無いと思いますが、
ゲーム中の特定の時だけスクロール出来なくさせたい時など、
スクリプトから設定することで効果を発揮します。
Transition
スクロールバーの状態に応じて、バーの色や画像などを個別に設定出来ます。
今回は ColorTint の場合のみ説明します。(状態に応じて色の遷移を行う)
・TargetGraphic
遷移対象となるグラフィックです。デフォルトではHandleが設定されています。
・NormalColor 〜 DisabledColor
それぞれの状態の時の色を設定します。
上から順に、通常時、ハイライト時、押下時、無効時の色を設定出来ます。
・ColorMultiplier
上記設定の明るさに倍率を設定します。暗い画像を用いる際に使うと効果的です。
・FadeDuration
遷移にかかる時間を設定します。
Navigation
実行中、方向キーによってオブジェクトに対するフォーカスを切り替える際の設定です。
今回は使用しないため無視して構わないと思います。
HandleRect
HandleのRectTransformを設定します。
Direction
スクロールバーの方向を設定します。
垂直方向のスクロールならBottom To Top、
水平方向ならLeft To Rightが一般的だと思われます。
Value
Handleの(初期)位置を0〜1の値により設定します。
Size
Handleのサイズを設定します。
NumberOfSteps
Handleの移動の際、段階を設けることが出来ます。
0、1なら滑らかに動き、それ以上の数の場合、数に応じて移動の段階が決定されます。
(例:3に設定した場合、初期位置、真ん中、終点の3箇所にしかHandleが移動しない)
OnValueChanged (Single)
Handleが移動した際に呼び出されるイベントを設定できます。
これらを本記事の目的に合わせて設定してみます。
…とはいえ、特に拘りがあるわけではないので、DirectionをBottom To Topに設定するだけにしておきます。
もちろん、このままではスクロールバーの位置がおかしいので、座標やサイズの調整は行います。
高さをViewAreaと同じ400に設定し、位置を右端に持ってきました。
ここで、少し話が逸れますが、UIを作る際に大事なポイントをご紹介します。
上の画像をよく見ると、今までの画像のRectTransformと違うところがあると思います。
そうです、前回の記事でチラっと触れた、Anchorの設定がされています。
このスクロールバーがどんな画面サイズでもViewAreaの右端に位置するようにするため設定したのです。
Anchorとは、親オブジェクトに対する中央座標のことでした。
つまりこのスクロールバーは、ViewAreaの右端中央を中心座標として座標を設定するようにしてあるのです。PosXもPosYも0になっていますが、中心座標(右端)=自分の座標 にしたいので、このようにしてあります。
ただし、Anchorを設定して座標を両方0にしただけでは、望む結果は得られません。
きっと、以下の画像のようになると思います。
スクロールバーが半分はみ出てしまっていますね。
実は先ほどのRectTransformには、もう一点、手を加えてありました。
それはPivotです。
Pivotは、オブジェクトの座標の起点のようなものだと思ってください。
Pivotは0〜1の値でX、Y座標それぞれに設定でき、デフォルトは0.5です。
Xの場合、0が左、1が右。Yの場合、0が下、1が上になります。
デフォルトのままの場合、Xの起点が0.5、つまり真ん中に設定されているので、スクロールバーのオブジェクトは自身のX座標の起点(=真ん中)と中心座標を合わせようとします。
すると、先ほどの画像のように右側半分がはみ出してしまうのです。
PivotのXを1(=右端)にすることで、右端が起点となるため、オブジェクトは右端と中心座標を合わせることになり、結果として少しもはみ出さずに表示されるのです。
このようにオブジェクトの各座標の起点 Pivotと、オブジェクトの中心座標Anchorの設定を行い、PosX、PosYを限りなく0に近づけることで、画面サイズが変動しても親オブジェクトとの位置関係を保つことが可能になります。
さて、大事な話とはいえ話が逸れてしまいましたが、これで任意の位置にスクロールバーを作成出来ました。
早速、実行してスクロールさせてみましょう。
そりゃ動きませんよね。
当然です、ScrollRectの方に、このスクロールバーを設定していないのです。
というわけで、ScrollRectにScrollbarオブジェクトを設定してみます。
今回は垂直方向のスクロールなので、VerticalScrollbarのみ設定します。
すると、Visibilityという項目が出現します。これは、スクロールバーの表示設定です。
Permanent
どんな時でもスクロールバーを表示します。
AutoHide
(今回の例で言えば)中身のボタンの数が少ないなど、
スクロールの必要が無い時にスクロールバーを非表示にします。
AutoHideAndExpandViewPort
AutoHideの機能に加え、スクロールの必要が無い時に
表示領域を拡大して表示します。
今回はAutoHideを選びました。
実際のゲームでも、スクロールバーはあるけどスクロールの必要が無いなんてUIはなんだか変な感じがしますよね。(※個人の感想です)
さて、これでようやくスクロールバーが真の力を発揮できます。
うまくいきましたね。
ちなみに、ScrollAreaのAnchorをcenter・top(上端真ん中)、Pivot(Y)を1にして、PosYを0にすることで、ScrollAreaのスクロール初期位置を一番上にしてあります。
(これを設定しないと、スクロールが真ん中から始まると思います)
まとめ
今回も長くなってしまいましたが、無事にスクロール機能を実装することが出来ました。
少しだけ、おさらいをしましょう。
・マスク処理はMaskまたはRectMask2Dコンポーネント
・スクロール領域にはScrollRectコンポーネント
・スクロールバーにはScrollbarコンポーネント
・AnchorとPivotを適切に設定して表示ずれを防ぎましょう
次回は、少し話題の違った記事を書きたいと思います。
それでは、長々とお付合いいただきありがとうございました。