神様は有休消化中です。

Unity関連の技術ネタを書いてます。

【Unity】【uGUI】リストビューに、セルが画面外から差し込まれるアニメーションをつける

uGUIで作ったリストビューに、セルが画面外から差し込まれるようなアニメーションを追加する方法を共有。
最終的にこんな感じになります。
f:id:appleorbit:20160402223716g:plain



サンプルのオブジェクト構成

f:id:appleorbit:20160402230709p:plain

実装

using UnityEngine;
using UnityEngine.UI;
using System.Linq;
using System.Collections;

using DG.Tweening;

public class TestScroll : MonoBehaviour
{
    // 子オブジェクトの取得が面倒なので拡張メソッドとプロパティで楽にする
    private GameObject CellOrigin    {get{ return gameObject.FindChild ("Cell", true);     }}
    private GameObject Content        {get{ return gameObject.FindChild ("Content", true);}}
    private GameObject Button        {get{ return gameObject.FindChild ("Button", true); }}

    private int cellcount = 0;
    private Coroutine animationCoroutine;

    private void Awake()
    {
        Button.GetComponent<Button> ().onClick.AddListener (OnClickRefresh);

        // スクロールビューをリフレッシュ
        RefreshScrollView ();
    }

    /// <summary>
    /// リフレッシュボタンが押された時の処理
    /// </summary>
    private void OnClickRefresh()
    {
        RefreshScrollView ();
    }

    /// <summary>
    /// スクロールビューをリフレッシュする
    /// </summary>
    private void RefreshScrollView()
    {
        if (animationCoroutine != null) {
            return;
        }

        RemoveAllCell ();

        const int AddCellCount = 6;
        GameObject origin = CellOrigin;
        GameObject content = Content;

        for (int i = 0; i < AddCellCount; i++) {
            cellcount++;
            GameObject cell = Instantiate (origin) as GameObject;
            cell.SetActive (true);
            cell.transform.SetParent (content.transform, false);
            cell.GetComponentInChildren<Text> ().text = "Cell : " + cellcount;
        }


        StartAnimation ();
    }

    /// <summary>
    /// 全セルを削除
    /// </summary>
    private void RemoveAllCell()
    {
        foreach (Transform child in Content.transform) {
            Destroy (child.gameObject);
        }
    }

    /// <summary>
    /// リストを差し込むアニメーションを開始する
    /// </summary>
    private void StartAnimation()
    {
        animationCoroutine = StartCoroutine (_StartAnimation());
    }

    /// <summary>
    /// アニメーション処理の実態
    /// </summary>
    /// <returns>The animation.</returns>
    private IEnumerator _StartAnimation()
    {
        // 1フレーム待つ(注1)
        yield return null;

        // Tweenアニメーションを開始
        Sequence sequence = DOTween.Sequence ();
        sequence.AppendInterval (0.05f);

        foreach (Transform cell in Content.transform) {
            Tween animation = GenerateCellTweenAnimation (cell.gameObject, 0.2f);
            sequence.Append (animation);
            sequence.AppendInterval (0.05f);
        }

        // 終了したらコルーチンを削除
        sequence.OnComplete (()=> {
            animationCoroutine = null;
        });
        sequence.Play ();
    }

    /// <summary>
    /// セルのTweenアニメーションを生成する
    /// </summary>
    private Tween GenerateCellTweenAnimation(GameObject cell, float time)
    {
        // 見た目用のオブジェクトからRectTRansformを取得
        RectTransform rt = cell.GetChildComponent<RectTransform> ("CellView");

        // 画面外の位置を指定する
        Vector3 start = rt.localPosition;
        start.x += Screen.width;

        Vector3 end = rt.localPosition;

        rt.localPosition = start;
        return rt.DOLocalMove (end, time).SetEase (Ease.InCirc);
    }
}


/// <summary>
/// ゲームオブジェクトの拡張
/// </summary>
public static class GameObjectExtensions
{
    /// <summary>
    /// 子オブジェクト全てから指定した名前に一致するオブジェクトを取得
    /// </summary>
    public static GameObject FindChild(this GameObject go, string name, bool includeInactive = false)
    {
        Transform[] children = go.GetComponentsInChildren<Transform> (includeInactive);
        Transform[] filtered = children.Where (o => o.name == name).ToArray ();
        if (filtered.Length > 0) {
            return filtered[0].gameObject;
        }
        return null;
    }

    /// <summary>
    /// 指定した名前の子オブジェクトについているコンポーネントを取得する
    /// </summary>
    public static Type GetChildComponent<Type>(this GameObject go, string name)
    {
        GameObject child = go.FindChild (name);
        if (child == null) {
            return default(Type);
        }
        return child.GetComponent<Type> ();
    }
}     
注1:セルオブジェクトを生成した次のフレームでTweenを開始する

正確にはセルのDestroy->生成->Tweenアニメーション開始を1フレーム内で行うと、Tweenアニメーションが再生されない。
これは、Destroyしたオブジェクトが消されるのは実際にはフレームの最後のため、新しいセルオブジェクトのアニメーションを正しく生成できないため。