読者です 読者をやめる 読者になる 読者になる

uGUIのGridLayoutGroupで子要素を使い回し

Unity5のuGUIいいですね。
まだ暇な時間に標準機能を弄ってるだけなのですが、
特にスクロールリストが使いやすくて好きです。

今回はリストの要素を使いまわしするコンポーネントが欲しい
と感じたので作ってみました。

ただ、使い回しリストを作るだけだと二番煎じ感が強いので、
+αで非同期処理が入る場面も想定しての造りにしようと思います。

例えば、ソシャゲなどでよくあるキャラクター選択画面で
顔アイコンがずらりと並ぶ場面などで、
画像を読み込みながらスクロールしていくような感じですね。

予測される例外処理

可視範囲に入った瞬間に読み込み処理が入って
読み込みが終わる前に場所で使い回されてしまうと、
実装次第ではうまく表示されないなどの状態も予想されます。

要は、非同期処理の穴ですね。
読み込み中の要素は使い回さず、事が済むまで放置するように処理します。

もちろん、自分が一人で作っている分には
問題にすぐ気づけるので良いのですが、
複数人での開発となると話は別。

使ってもらう側としても、もっと気軽に、
もっと気持ちよくコーディングしてもらいたいものです。

プロトタイプ作ってみました

f:id:SprField:20170326212207g:plain

GIFアニメーションだとカクカクして分かりづらいかもしれませんが、一千個、一万個のオブジェクトも難なくスクロールする様を見るのは小気味よいですね。

github.com

 

 使い方

  1. Hierarchyで右クリック
  2. UI > Scroll View を選択
  3. Content オブジェクトに RecycleGridLayoutGroupコンポーネントを追加
  4. RecycleGridLayoutGroupコンポーネントに設定を入力
  5. 任意でソースコードから onVisibleListItem、onHideListItem にコールバック処理を記述する

以下、onVisibleListItem、onHideListItemコールバックのサンプル

void Start()
{
    RecycleGridLayoutGroup grid = target.GetComponent<RecycleGridLayoutGroup>();
    // リストの総数を設定
    grid.listItemCount = 1000;
    // コールバックイベントの設定
    grid.onVisibleListItem = OnVisibleListItem;
    grid.onHideListItem = OnHideListItem;
}

/// 対象のリスト要素が画面内に入った直後のコールバック
/// 引数 item には対象のリストの情報が入ったコンポーネント
/// complete は処理が完了したら必ず呼ぶこと(呼ばれるまで使いまわしの対象にならない)
private void OnVisibleListItem( ListItemContent item, Action complete )
{
    // item.indexでリストの何番目かをとってこれるので、データベースの参照などに
    Debug.Log( "リスト"item.index + "番目" );
    // ルートオブジェクトについたコンポーネントなのでFindChildで子要素を検索が可能
    RawImage img = item.transform.FindChild("icon").GetComponent<RawImage>();

    // キャラクターアイコンのテクスチャを読み込む処理があったとして・・・
    LoadCharacterIconTexture( chara_id, ( tex ) => {
        img.texture = tex;
        complete(); // 処理が終わったら必ず呼ぶ
    });
}

/// 対象のリスト要素が画面外に出ていった直後の処理
private void OnHideListItem( ListItemContent item )
{
    // 必要なら画面外に消えていった際の処理を書く
}


画像は読み込んでませんがランダムな時間に
プログレスが100%になる処理を入れてやってみました。
ちゃんと動いているみたいですね。よかったよかった。

f:id:SprField:20170327234555g:plain

残課題、未実装項目

こういうブログ書くの慣れてないもので、まとめるだけで時間かかっちゃいましたが
残課題まとめておきます。消化したら順次打ち消し線入れていきます。

  • View全体を更新する関数を用意するの忘れてた
  • 末尾以外の要素が削除/追加された場合の処理(上記、全体更新の処理を入れてやる必要がある)
  • 上から下方向へのスクロールしか出来ない

3番目は気が向いたらやります。多分。

ご意見ご要望などがございましたらどうぞお気軽に~

2017/03/28更新。とりあえず2点は消化。

RectTransformのsizeDeltaが0になる

f:id:SprField:20170319230556p:plain

RectTransform の設定で幅を stretch させたとき、
オブジェクトのサイズの取得をしようとすると
なんと幅が 0.0f で返ってくる。

サイズを変更するときは sizeDelta で問題なかった。
stretch じゃなければ、sizeDelta で幅を取得することも問題なかった。
が、幅を stretch させた瞬間、やっぱり 0.0f が返ってくる。

RectTransform rectTransform = GetComponent<RectTransform>();
Debug.Log( "sizeDelta = " + rectTransform.sizeDelta );

▼出力結果
sizeDelta = (0.0, 0.0)

 他のパラメータを探ってみる

とりあえずなんでもいいから手当たり次第に
他のパラメータで取得できないかログに吐き出してみる。

Debug.Log( "anchor : " + rectTransform.anchorMin + " - " + rectTransform.anchorMax );
Debug.Log( "offset : " + rectTransform.offsetMin + " - " + rectTransform.offsetMax );
Debug.Log( "anchorPos : " + rectTransform.anchoredPosition );
Debug.Log( "rect : " + rectTransform.rect );
Debug.Log( "localScale : " + rectTransform.localScale );
Debug.Log( "sizeDelta : " + rectTransform.sizeDelta );

▼出力結果
anchor : (0.0, 0.0) - (1.0, 1.0)
offset : (0.0, 0.0) - (0.0, 0.0)
anchorPos : (0.0, 0.0)
rect : (x:0.00, y:-407.00, width:453.00, height:407.00)
localScale : (1.0, 1.0, 1.0)
sizeDelta : (0.0, 0.0)

あった……ありました……
rect の width height だとちゃんと取れるみたいですね。

初投稿とご挨拶

f:id:SprField:20170318182645j:plain
f:id:SprField:20170318184656p:plain Photo by Nehemias Godinez


初めまして。SprFieldと申します。

Unity5を触ってゲームっぽい画面作って遊んでたら楽しくなってきたので
その過程で提供できそうな情報があったら
このブログに載せていこうかなと思っています。

願わくは……あわよくば……!
そのままの勢いでゲーム作れることを期待して
気分で書き綴っていこうと思います。

意思が段々と弱まっているあたりが完全にフラグですが、
生ぬるい目で見守っていただけたら幸いです。