2016年6月18日土曜日

GameObjectをキャッシュして使い回すScrollRect

Unity3DのScrollRect.
DynamicScrollRectから見れば, GameObjectがキャッシュされているかは知らなくてよいこと.
/*
This is free and unencumbered software released into the public domain.
For more information, please refer to <http://unlicense.org/>
*/
public class ObjectPool<T> where T : UnityEngine.Component
{
    private int size_;
    private T[] objects_;
    private Transform parent_;
    private GameObject prefab_;

    public ObjectPool(int capacity, Transform parent, GameObject prefab)
    {
        size_ = 0;
        objects_ = new T[capacity];
        parent_ = parent;
        prefab_ = prefab;
    }

    public T popOrInstantiate()
    {
        if(size_<=0){
            GameObject obj = GameObject.Instantiate<GameObject>(prefab_);
            return obj.GetComponent<T>();
        }
        --size_;
        return objects_[size_];
    }

    public void push(T obj)
    {
        if(objects_.Length<=size_){
            System.Array.Resize<T>(ref objects_, size_+8);
        }
        obj.transform.SetParent(parent_);
        objects_[size_] = obj;
        ++size_;
    }
}

public class DynamicScrollRect : UnityEngine.UI.ScrollRect
{
    public interface IListAdaptor
    {
        int getOverCount();
        int size();
        float getTotalHeight();
        float getHeight(int index);
        float getTop(int index);
        RectTransform create(int index);
        void destroy(RectTransform rect);
    };


    public class TestAdaptor : IListAdaptor
    {
        public const float Height = 32.0f;

        private ObjectPool<RectTransform> pool_;
        private string[] contents_;

        public TestAdaptor(Transform parent, GameObject prefab)
        {
            pool_ = new ObjectPool<RectTransform>(16, parent, prefab);
            contents_ = new string[128];
            for(int i=0; i<contents_.Length; ++i){
                contents_[i] = string.Format("{0}", i);
            }
        }

        public int getOverCount()
        {
            return 2;
        }

        public int size()
        {
            return contents_.Length;
        }

        public float getTotalHeight()
        {
            return contents_.Length * Height;
        }

        public float getHeight(int index)
        {
            return Height;
        }

        public float getTop(int index)
        {
            return index * Height;
        }

        public RectTransform create(int index)
        {
            RectTransform rect = pool_.popOrInstantiate();
            rect.gameObject.SetActive(true);
            UnityEngine.UI.Text text = rect.GetComponent<UnityEngine.UI.Text>();
            text.text = contents_[index];
            return rect;
        }

        public void destroy(RectTransform rect)
        {
            rect.gameObject.SetActive(false);
            pool_.push(rect);
        }
    }

    public const float Epsilon= 1.0e-5f;
    public GameObject prefab_;

    private IListAdaptor adaptor_;
    private float prevPosition_;
    private struct Item
    {
        public int index_;
        public RectTransform rect_;

        public Item(int index, RectTransform rect)
        {
            index_ = index;
            rect_ = rect;
        }
    };

    private System.Collections.Generic.List<Item> items_ = new System.Collections.Generic.List<Item>(8);

    public void initialize(IListAdaptor adaptor)
    {
        if(null != adaptor_){
            clear();
            this_.onValueChanged.RemoveAllListeners();
        }
        adaptor_ = adaptor;
        if(null != adaptor_){
            RectTransform thisRect = GetComponent();
            this.content.sizeDelta = new Vector2(thisRect.rect.width, adaptor_.getTotalHeight());
            relayout(thisRect.rect.height);
            this.onValueChanged.AddListener(onScroll);
        }
        prevPosition_ = this.normalizedPosition.y;
    }

    public void relayout()
    {
        relayout(this.viewport.rect.height);
    }

    public void relayout(float height)
    {
        float minY = this.content.anchoredPosition.y - Epsilon;
        float maxY = minY + height + Epsilon;

        int start = int.MaxValue;
        int end = int.MinValue;
        float y = 0.0f;
        for(int i=0; i<adaptor_.size(); ++i){
            float ny = y + adaptor_.getHeight(i);
            if(minY<=ny && y<=maxY){
                start = Mathf.Min(start, i);
                end = Mathf.Max(end, i);
            }
            y = ny;
        }

        if(end<start){
            clear();
            return;
        }
        create(start, end);
    }

    public void updateLayout()
    {
        if(items_.Count<=0){
            relayout();
            return;
        }
        float minY = this.content.anchoredPosition.y - Epsilon;
        float maxY = minY + this.viewport.rect.height + Epsilon;

        int start = int.MaxValue;
        int end = int.MinValue;

        int begin = Mathf.Max(0, items_[0].index_-adaptor_.getOverCount());
        int term = Mathf.Min(adaptor_.size(), items_[items_.Count-1].index_+adaptor_.getOverCount());
        float y = adaptor_.getTop(begin);
        for(int i=begin; i<term; ++i){
            float ny = y + adaptor_.getHeight(i);
            if(minY<=ny && y<=maxY){
                start = Mathf.Min(start, i);
                end = Mathf.Max(end, i);
            }
            y = ny;
        }
        if(end<start){
            clear();
            return;
        }
        create(start, end);
    }

    public void clear()
    {
        for(int i=0; i<items_.Count; ++i) {
            adaptor_.destroy(items_[i].rect_);
        }
        items_.Clear();
    }

    private void pushBack(int index)
    {
        RectTransform rect = adaptor_.create(index);
        rect.SetParent(this.content, false);
        rect.transform.localPosition = Vector3.zero;
        rect.localScale = Vector3.one;
        rect.SetAsLastSibling();
        items_.Add(new Item(index, rect));
    }

    private void pushFront(int index)
    {
        RectTransform rect = adaptor_.create(index);
        rect.SetParent(this.content, false);
        rect.transform.localPosition = Vector3.zero;
        rect.localScale = Vector3.one;
        rect.SetAsFirstSibling();
        items_.Insert(0, new Item(index, rect));
    }

    private void create(int start, int end)
    {
        for(int i=0; i<items_.Count;){
            if(items_[i].index_<start || end<items_[i].index_){
                adaptor_.destroy(items_[i].rect_);
                items_.RemoveAt(i);
            }else{
                ++i;
            }
        }

        for(int i=start; i<=end; ++i){
            if(items_.Count<=0 || items_[items_.Count-1].index_<i){
                pushBack(i);
            }
        }
        for(int i=end; start<=i; --i){
            if(items_.Count<=0 || i<items_[0].index_){
                pushFront(i);
            }
        }

        if(items_.Count<=0){
            return;
        }

        float contentHeight = this.content.rect.height;
        float y = contentHeight*0.5f - adaptor_.getTop(items_[0].index_);

        for(int i=0; i<items_.Count; ++i){
            float height = adaptor_.getHeight(items_[i].index_);
            //items_[i].rect_.anchoredPosition = new Vector2(0.0f, y-0.5f*height);
            items_[i].rect_.anchoredPosition = new Vector2(0.0f, y);
            y -= height;
        }
    }

    new System.Collections.IEnumerator Start()
    {
        base.Start();
        yield return null;
        initialize(new TestAdaptor(transform, prefab_));
    }

    void onScroll(Vector2 position)
    {
        float pos = this.normalizedPosition.y;
        float diff = Mathf.Abs(pos-prevPosition_) + Epsilon;
        prevPosition_ =  pos;
        float height;
        float contentHeight = this.content.rect.height;
        if(Epsilon<contentHeight){
            height = this.viewport.rect.height/this.content.rect.height;
        }else{
            height = 0.0f;
        }
        if(height<=diff){
            relayout();
        }else{
            updateLayout();
        }
    }
}

0 件のコメント:

コメントを投稿