스크롤 뷰

2019. 7. 26. 18:14무한 스크롤 뷰

항목 생성

유저가 소유한 아이템을 모두 생성하지 않고 보이는 부분만 아이템 항목을 생성합니다. 생성을 해야하는 항목이 100개이든 1000개이든 상관없이 유저는 설정한 영역안의 항목들만 봅니다.

InLine1

In아이템1

In아이템2

In아이템3

In아이템4

InLine2

In아이템1

In아이템2

In아이템3

In아이템4

OutLine3

Out아이템1

Out아이템2

Out아이템3

Out아이템4

유저가 소유한 아이템을 모두 생성하지 않고 InLine1 ~ InLine2는 게임에서 보이는 영역과 유저에게 보이지는 않지만 드래그를 했을 경우 하나의 여분의 Line(OutLine3)을 생성을 합니다.

생성할 때 소유하고 있는 항목들의 개수과 관계없이 해당되는 행들의 항목들 + 여유분의 행의 항목들을 모두 생성합니다. 예를 들면 보여지는 행이 4개이고 행마다 가지는 항목의 개수가 5개이면 [(보여지는 행 4개 + 여유분의 행 1개) * 행마다 가지는 항목 수 5개]로써 총 25개를 모두 생성합니다.

왜 미리 생성하나요?

미리 생성하는 이유는 무한 스크롤 뷰에서 보이는 부분만 별도로 처리하기 위함입니다. 초기화할 때는 초기화, 갱신할 때는 스크롤 뷰 안에 갱신 구현 코드를 내장시키는 것이 아니라 항목들의 상태에 따라서 처리하는 방식을 생각하게 되다 보니 미리 생성하게 되었습니다.

스크롤 할 때 이미지가 교체되는 비용은 어떤가요?

제작하면서 이미지를 교체하는 비용에 대해서 생각을 하다가 문득 텍스처 패킹이 떠오르게 되어서 비용에 대해서는 고민을 하지 않게 되었습니다.

초기화

public void Initialize(EndlessToken token)
{
    token.ItemCountInField = CalculateItemCountInField(token);
    token.VisibleFieldCount = CalculateVisibleFieldCount(token);
 
    if (mBatch == null)
    {
        mBatch = UIEndlessBatch.CreateBatch(gameObject);
        mBatch.Initialize(token);
    }
 
    if (mMoveField == null)
    {
        mMoveField = new StrategyMoveField();
    }
 
    Panel.onClipMove = OnClipMove;
 
    InitializeFields(token);
}

Token의 ItemCountInField와 VisibleFieldCount 변수는 스크롤 뷰에서 초기화가 이루어집니다. 보여지는 영역을 설정하는 Panel로 설정하는데 이 Panel을 가지고 있는 객체가 스크롤 뷰이기 떄문입니다.

ItemCountInField

한 행에 표시되는 항목의 개수입니다.

VisibleFieldCount

보여지는 행 + 여분의 행의 개수입니다.

Batch

행의 항목들을 나열하기 위한 스크립트입니다.

MoveField

스크롤할 때 항목을 교체하기 위한 알고리즘 객체입니다.

OnClipMove

UIScrollView에서 스크롤을 감지하면 호출되는 함수입니다.

CalculateItemCountInField 함수

한 행에 표시되는 항목의 개수를 구할 때 Horizontal이면 위에서 아래로 나열되므로 Height, Vertical이면 왼쪽에서 오른쪽으로 나열되므로 Width를 활용하여 처리합니다.

private int CalculateItemCountInField(EndlessToken token)
{
    float value = -1;
 
    switch (movement)
    {
        case Movement.Horizontal:
            value = Panel.GetViewSize().y / token.ItemHeight;
            break;
 
        case Movement.Vertical:
            value = Panel.GetViewSize().x / token.ItemWidth;
            break;
    }
 
    return (int)value;
}

CalculateVisibleFieldCount 함수

보여지는 행 + 여분의 행의 개수를 구하는 함수입니다. 방향에 따른 축을 바탕으로 계산하면 되지만 마지막에 여유분을 추가하기 위해서 반드시 1을 증가해야합니다. 하지만 단순히 Ceil을 할 경우 value가 딱 떨어지는 수(200 / 50 = 4)일 경우 Ceil이 되지 않으므로 추가 작업을 해줘야합니다.

private int CalculateVisibleFieldCount(EndlessToken token)
{
    float value = 0f;
 
    switch (movement)
    {
        case Movement.Horizontal:
            value = Panel.GetViewSize().x / token.ItemWidth;
            break;
 
        case Movement.Vertical:
            value = Panel.GetViewSize().y / token.ItemHeight;
            break;
    }
 
    //value가 정수일 경우 Ceil을 해도 증가되지 않으므로 임시값을 더함으로써 매칭시켜줍니다.
    value += 0.001f;
 
    return Mathf.CeilToInt(value);
}
 

InitializeFields 함수

각 행을 초기화하는 함수입니다. 여기서 중요한 점은 행에 대한 초기화를 할 뿐 항목에 대한 갱신을 같이 처리하지 않는 점 입니다. 이렇게 함으로써 초기화와 갱신을 분리하여 처리할 수 있습니다.

private void InitializeFields(EndlessToken token)
{
    if (mFields == null)
    {
        mFields = new List<UIEndlessField>();
    }
 
    mFields.Clear();
 
    GameObject go = null;
    GameObject parent = mBatch.gameObject;
    UIEndlessField field = null;
 
    for (int i = 0; i < token.VisibleFieldCount; ++i)
    {
        go = NGUITools.AddChild(
            parent,
            Resources.Load<GameObject>(token.FieldPath));
 
        if (go != null)
        {
            field = go.GetComponent<UIEndlessField>();
 
            if (field != null)
            {
                field.Initialize(i, token);
                mFields.Add(field);
            }
        }
    }
 
    mBatch.Reposition(token);
}

활성화

활성화가 되면 바운드 최대 영역을 계산하고 항목들을 갱신합니다. 바운드 최대 영역을 활성화가 되는 시점에 갱신하는 이유는 항목의 개수가 추가/삭제될 경우 최대 영역이 바뀔 수 있게 때문입니다. 또한 항목을 갱신하기 전에 해당 항목을 표시하는 공통 클래스에 항목에 관한 리스트를 등록해야 갱신이 됩니다.

private void OpenInvView(int tapIndex)
{
    UIEquipItem.SSetItems(GetItems(tapIndex));
 
    m_EndlessViews[tapIndex].Open(tapIndex, GetItems(tapIndex).Count);
}
 

위의 코드는 외부에서 호출하는 코드입니다. 뷰를 활성화하기전에 다음과 같이 공통 클래스에 항목 리스트를 등록해야합니다. 위의 코드에 대해서는 View를 참고해주세요.

Open함수가 호출되면 바운드의 최대 영역을 계산하고 항목들을 갱신합니다. 항목을 갱신하기에 앞서 스크롤된 객체의 좌표를 원상 복귀를 시키며 Spring을 사용하고 있으면 해제합니다. 이 작업을 해주지 않으면 탭을 클릭해서 스크롤 뷰를 갱신할 때 항목 개수가 다르면 제대로 표시되지 않는 현상과 계속해서 스피링 효과가 발생하는 현상이 생깁니다.

public void Open(EndlessToken token)
{
    base.MoveAbsolute(-transform.position);
    DisableSpring();
 
    mToken = token;
    mBounds = CalculateBounds(mBatch.GetPosition(Space.Self), token);
 
    _Update(token);
}
 
public void _Update(EndlessToken token)
{
    for (int i = 0; i < mFields.Count; ++i)
    {
        GetField(i)._Update(i, token);
    }
}
 
public UIEndlessField GetField(int fieldIndex)
{
    return mFields[fieldIndex];
}
 

바운드 최대 영역 계산

바운드라고 해도 결국은 사각형의 영역을 구하는 것 입니다. 즉 사각형의 가로 길이, 세로 길이를 구해서 시작 지점(center), 끝 지점(bottomRight)를 지정해주면 됩니다. Vertical 기준으로 가로, 세로를 구하면 다음과 같습니다.

Vertical
가로

시작 지점의 x축 + (항목의 가로 길이 * 한 행의 개수)

= center.x + (itemWidth * token.ItemCountInField)

세로

시작 지점의 y축 + (항목의 세로 길이 * 스크롤의 마지막 행의 값)

= center.y + (itemHeight * token.LastFieldInex + 1d

 

private Bounds CalculateBounds(Vector3 center, EndlessToken token)
{
    Bounds bounds;
    Vector3 bottomRight = Vector3.zero;
 
    center.x = -(panel.GetViewSize().x * 0.5f);
    center.y = (panel.GetViewSize().y * 0.5f);
 
    switch (movement)
    {
        case Movement.Horizontal:
            bottomRight.x = center.x + (token.ItemWidth * token.ItemCountInField);
            bottomRight.y = center.y + (-token.ItemHeight * (token.LastFieldIndex + 1));
            bottomRight.z = transform.localPosition.z;
            break;
 
        case Movement.Vertical:
            bottomRight.x = center.x + (token.ItemWidth * token.ItemCountInField);
            bottomRight.y = center.y + (-token.ItemHeight * (token.LastFieldIndex + 1));
            bottomRight.z = transform.localPosition.z;
            break;
    }
 
    bounds = new Bounds(center, Vector3.zero);
    bounds.Encapsulate(bottomRight);
 
    return bounds;
}
 

행 교체

행을 교체하는 방법을 고민하다가 처음 시도한 것은 행의 상단을 하위로 이동시키고 갱신하는 방법을 사용하였습니다. 하지만 이 경우 손이 많이 갈 뿐더러 일반 드래그와 순간 드래그를 구분하는 로직이 필요했습니다. 자세한 것은 링크을 참고해주세요.(현재 사용되지 않습니다.)

고민을 하다가 게임은 "어짜피 눈속임이다"라는 것을 생각하게 되었고 상단으로 이동한 행이 범위를 벋어나면 다시 재배치해서 현재 행의 값에 맞춰 항목들만 갱신하면 되는 것을 생각하게 되었습니다

public class StrategyMoveField
{
    public void ComeBackField(UIEndlessScrollView view, EndlessToken token)
    {
        if (view.Batch != null)
        {
            view.Batch.Reposition(token);
            view._Update(token);
        }
    }
}

'무한 스크롤 뷰' 카테고리의 다른 글

항목을 표현하는 클래스  (0) 2019.07.26
스크롤 뷰 최적화(이전 버전 용)  (0) 2019.07.26
항목  (0) 2019.07.26
토큰  (0) 2019.07.26
포함 객체  (0) 2019.07.26