시행착오

02.25 회고 + Scroll View 응용

Cadi 2025. 2. 26. 01:39

내 질문  :

답변 : 안쓰셨던 이유가 따로 있다. 나중에 알려주신다고 하셨다. 

 

내 질문 : 

답변 : SNS에서는 특정 사이즈에 도달하면 리사이징 하는 등이 필요하기도 하겠지만

         지금은 필요없다, 더해서 Content Size를 임의로 주게 되면 , 스크롤 바 사이즈가 차이가 나게 된다.

(스크롤 바가 작아지는 것을 생각해보자, Content Size를 전체 갯수대로 해야지 스크롤 바를 통해 예측도 가능하다)

메모리 등의 문제는 따로 없다 ! 

 

그러니까 SNS 등과 같이 거의 무한에 가까운 스크롤일 때만 타협하면 된다. 

 

https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/script-ContentSizeFitter.html

 

Content Size Fitter | Unity UI | 2.0.0

Content Size Fitter Properties Property: Function: Horizontal Fit How the width is controlled.     Unconstrained Do not drive the width based on the layout element.     Min Size Drive the width based on the minimum width of the layout element.   

docs.unity3d.com

 

코드로 바로 뚝딱뚝딱은 힘들다.

종이에 써 봐라 !

 

 

가장 위에 있는 셀과 가장 마지막에 있는 셀을 기준으로 동작시킨다. 


로드데이타는 필요한 데이터를 가져오는 것

리로드데이타는 읽을 준비를 하는 것 (렌더링도 ! )

 

제일 먼저 해야 하는 일은 Content의 높이를 설정하는 일.

var contentSizeDelta = _scrollRect.content.sizeDelta;
contentSizeDelta.y = _items.Count * cellHeight;
_scrollRect.content.sizeDelta = contentSizeDelta;

 

이제, Content의 높이를 설정했으니 '화면에 보이는 영역에 Cell 추가'를 해야함.

좌표를 토대로 '어떤 셀을 추가하고 삭제할 것인가'를 알아야 한다.

 

그래서 새로운 함수로 어떤 셀 (몇 번부터 몇 번 셀이 보여지면 좋을 것 같다) 는 것을 가져옴

 

새로운 타입 : Rect -> 4가지 값을 담을 수 있음 x y w h

 

var visibleRect = new Rect(
    _scrollRect.content.anchoredPosition.x,
    _scrollRect.content.anchoredPosition.y,
    _rectTransform.rect.width,
    _rectTransform.rect.height);

 

이것을 기준으로 Index를 계산해야함.

 

private (int startIndex, int endIndex) GetVisibleIndexRange()
{
    var visibleRect = new Rect(
        _scrollRect.content.anchoredPosition.x,
        _scrollRect.content.anchoredPosition.y,
        _rectTransform.rect.width,
        _rectTransform.rect.height);
    // 스크롤 위치에 따른 시작 인덱스 계산
    var startIndex = Mathf.FloorToInt(visibleRect.y / cellHeight);
    // 화면에 보이게 될 Cell 개수 게산
    int visibleCount = Mathf.CeilToInt(visibleRect.y / cellHeight);
    
    
    
    // 버퍼 추가
    
    startIndex = Mathf.Max(0, startIndex-1);
    visibleCount += 2; 
    return (startIndex, startIndex + visibleCount - 1 );
}

 

private void ReloadData()
    {
        _visibleCells = new LinkedList<Cell>();
        var contentSizeDelta = _scrollRect.content.sizeDelta;
        contentSizeDelta.y = _items.Count * cellHeight;
        _scrollRect.content.sizeDelta = contentSizeDelta;
        
        //화면에 보이는 영역에 셀 추가
        var (startIndex, endIndex) = GetVisibleIndexRange();
        var maxEndIndex = Mathf.Min(endIndex, _items.Count - 1);

        
        for (int i = startIndex; i < maxEndIndex; i++)
        {
            //셀 만들기
            var cellObject = ObjectPool.Instance.GetObject();
            var cell = cellObject.GetComponent<Cell>();
            cell.SetItem(_items[i]);
            cell.transform.localPosition = new Vector3(0, -i * cellHeight, 0);
            _visibleCells.AddLast(cell);
        }
    }

Mask를 끄면 화면에 벗어난 영역까지 보임 = 테스트 할 때 유용

 

 

여러개의 데이터를 표현하기 적합한 자료구조가 있지만,

직므은 여러개의 데이터들을 한 곳에 모아두기만 하면 됨.  딱 하나 중요한 것은 필요할 때 '특정한 인덱스'의 아이템을 화면에 출력하고 싶음. 그러니까 인덱스를 갖고 있는 배열이나 리스트도 적합하지만 

'링크드 리스트'를 사용해 중간에 어떤 데이터가 삽입 - 삭제도 용이하고 !!

 

 

public void OnValueChanged(Vector2 value)
{
    Debug.Log("## + value");
}

 

맨 위는 1, 맨 밑은 0의 값을 갖는 value이다. 

 

public void OnValueChanged(Vector2 value)
{
    
    
    
    //위로 올라가는 중
    if (_lastYValue < value.y)
    {
        //위로 
        var firstCell = _visibleCells.First.Value;
       
    }
    else
    {
        //아래로 스크롤
    }
    // 같은 경우는 OnvalueChanged가 따로 호출 X
    _lastYValue = value.y;
}

이런 식으로 올라갈 때와 내려갈 때를 나눠서 체크하려 한다.

그런데 문제는 이전에( 원래 ) 의 첫 번째 셀의 index를 모른다는 것이다.

using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class Cell : MonoBehaviour
{
    [SerializeField] private Image image;
    [SerializeField] private TMP_Text titleText;
    [SerializeField] private TMP_Text subtitleText;

    public int Index { get; private set; }
    public void SetItem(Item item, int index)
    {
       // image.sprite = Resources.Load<Sprite>(item.imageFileName);
        titleText.text = item.title;
        subtitleText.text = item.subtitle;
        
        Index = index;
    }
}

 

Cell에 index를 설정해주어 자기 자신의 인덱스를 갖고 있게 한다.

 

public void OnValueChanged(Vector2 value)
{
    //위로 올라가는 중
    if (_lastYValue < value.y)
    {
        //위로 
        var firstCell = _visibleCells.First.Value;
        var newFirstIndex = firstCell.Index - 1;
        
        // 1. 상단에 새로운 셀이 필요한지 확인 후 필요하면 추가
        //newFirstIndex가 만들어져야 할 Cell인지 확인
        if (IsVisibleIndex(newFirstIndex))
        {
            var cell = ObjectPool.Instance.GetObject().GetComponent<Cell>();
            cell.SetItem(_items[newFirstIndex], newFirstIndex);
            cell.transform.localPosition = new Vector3(0, -newFirstIndex * cellHeight, 0);
            _visibleCells.AddFirst(cell);

        }
        // 2. 하단에 있는 셀이 화면에서 벗어나면 제거
        var lastCell = _visibleCells.Last.Value;
        if (!IsVisibleIndex(lastCell.Index))
        {
            ObjectPool.Instance.ReturnObject(lastCell.gameObject);
            _visibleCells.RemoveLast();
        }
    }
    else
    {
        //아래로 스크롤
        var lastCell = _visibleCells.Last.Value;
        var newLastIndex = lastCell.Index + 1;

        if (IsVisibleIndex(newLastIndex))
        {
            var cell = ObjectPool.Instance.GetObject().GetComponent<Cell>();
            cell.SetItem(_items[newLastIndex], newLastIndex);
            cell.transform.localPosition = new Vector3(0, -newLastIndex * cellHeight, 0);
            _visibleCells.AddLast(cell);
        }
        
        var firstCell = _visibleCells.First.Value;
        if (!IsVisibleIndex(firstCell.Index))
        {
            ObjectPool.Instance.ReturnObject(firstCell.gameObject);
            _visibleCells.RemoveFirst();
        }
        
        
    }
    // 같은 경우는 OnvalueChanged가 따로 호출 X
    _lastYValue = value.y;
}

 

설정해준 Index를 바탕으로 데이터를 !!

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(ScrollRect))]
[RequireComponent(typeof(RectTransform))]
public class ScrollViewController : MonoBehaviour
{
    // [SerializeField] private GameObject cellPrefab;
    [SerializeField] private float cellHeight;

    private ScrollRect _scrollRect;
    private RectTransform _rectTransform;

    private List<Item> _items; // cell에 표시할 아이템 정보들
    private LinkedList<Cell> _visibleCells; //화면에 표시되고 있는 Cell 정보

    private float _lastYValue = 1f;

    private void Awake()
    {
        _scrollRect = GetComponent<ScrollRect>();
        _rectTransform = GetComponent<RectTransform>();
    }

    private void Start()
    {
        LoadData();
    }

    /// <summary>
    /// 현재 보여질 Cell 인덱스를 반환하는 메서드
    /// </summary>
    /// <returns>startIndex : 가장 위에 표시될 Cell 인덱스, endIndex : 가장 아래 표시될 Cell Index</returns>
    private (int startIndex, int endIndex) GetVisibleIndexRange()
    {
        var visibleRect = new Rect(
            _scrollRect.content.anchoredPosition.x,
            _scrollRect.content.anchoredPosition.y,
            _rectTransform.rect.width,
            _rectTransform.rect.height);
        // 스크롤 위치에 따른 시작 인덱스 계산
        var startIndex = Mathf.FloorToInt(visibleRect.y / cellHeight);
        // 화면에 보이게 될 Cell 개수 게산
        int visibleCount = Mathf.CeilToInt((visibleRect.y + visibleRect.height) / cellHeight);


        // 버퍼 추가

        startIndex = Mathf.Max(0, startIndex - 1);
        visibleCount += 2;
        return (startIndex, startIndex + visibleCount - 1);
    }

    
    /// <summary>
    /// 특정 인덱스가 화면에 보여야 하는지 여부를 판단하는 메서드
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    private bool IsVisibleIndex(int index)
    {
        // GetVisibleIndexRange는 다 관심없고 스크롤 할 때 스크롤한 영역만큼을 보고
        // 몇 번부터 몇 번이다 알려주기만 함.
        var (startIndex, endIndex) = GetVisibleIndexRange();
        endIndex = Mathf.Min(endIndex, _items.Count - 1);
        
        return startIndex <= index && index <= endIndex;
    }
    /// <summary>
    /// _items에 있는 값을 Scroll View에 표시하는 함수
    /// _items에 새로운 값이 추가되거나, 기존 값이 삭제되면 호출됨
    /// </summary>
    private void ReloadData()
    {
        _visibleCells = new LinkedList<Cell>();
        var contentSizeDelta = _scrollRect.content.sizeDelta;
        contentSizeDelta.y = _items.Count * cellHeight;
        _scrollRect.content.sizeDelta = contentSizeDelta;

        //화면에 보이는 영역에 셀 추가
        var (startIndex, endIndex) = GetVisibleIndexRange();
        var maxEndIndex = Mathf.Min(endIndex, _items.Count - 1);


        for (int i = startIndex; i < maxEndIndex; i++)
        {
            //셀 만들기
            var cellObject = ObjectPool.Instance.GetObject();
            var cell = cellObject.GetComponent<Cell>();
            cell.SetItem(_items[i],i);
            cell.transform.localPosition = new Vector3(0, -i * cellHeight, 0);
            _visibleCells.AddLast(cell);
        }
    }

    private void LoadData()
    {
        _items = new List<Item>();
        for (int i = 0; i < 40; i++)
        {
            var item = new Item()
            {
                imageFileName = $"item{i}",
                subtitle = $"subtitle{i}",
                title = $"title{i}",
            };
            _items.Add(item);
        }
        ReloadData();
    }

    #region Scroll Rect Events

    public void OnValueChanged(Vector2 value)
    {
        //위로 올라가는 중
        if (_lastYValue < value.y)
        {
            //위로 
            var firstCell = _visibleCells.First.Value;
            var newFirstIndex = firstCell.Index - 1;
            
            // 1. 상단에 새로운 셀이 필요한지 확인 후 필요하면 추가
            //newFirstIndex가 만들어져야 할 Cell인지 확인
            if (IsVisibleIndex(newFirstIndex))
            {
                var cell = ObjectPool.Instance.GetObject().GetComponent<Cell>();
                cell.SetItem(_items[newFirstIndex], newFirstIndex);
                cell.transform.localPosition = new Vector3(0, -newFirstIndex * cellHeight, 0);
                _visibleCells.AddFirst(cell);

            }
            // 2. 하단에 있는 셀이 화면에서 벗어나면 제거
            var lastCell = _visibleCells.Last.Value;
            
            if (!IsVisibleIndex(lastCell.Index))
            {
                ObjectPool.Instance.ReturnObject(lastCell.gameObject);
                _visibleCells.RemoveLast();
            }
        }
        else
        {
            //아래로 스크롤
            var lastCell = _visibleCells.Last.Value;
            var newLastIndex = lastCell.Index + 1;

            if (IsVisibleIndex(newLastIndex))
            {
                var cell = ObjectPool.Instance.GetObject().GetComponent<Cell>();
                cell.SetItem(_items[newLastIndex], newLastIndex);
                cell.transform.localPosition = new Vector3(0, -newLastIndex * cellHeight, 0);
                _visibleCells.AddLast(cell);
            }
            
            var firstCell = _visibleCells.First.Value;
            if (!IsVisibleIndex(firstCell.Index))
            {
                ObjectPool.Instance.ReturnObject(firstCell.gameObject);
                _visibleCells.RemoveFirst();
            }
            
            
        }
        // 같은 경우는 OnvalueChanged가 따로 호출 X
        _lastYValue = value.y;
    }

    #endregion
}

 

코드 전문인데 오류가 있다. 

점심 시간에 AnchoredPosition으로 바꿔도 정상 동작하나 찾아봤는데 안되길래 anchoredPositon 문제인줄 알았다.

 

private (int startIndex, int endIndex) GetVisibleIndexRange()
{
    var visibleRect = new Rect(
        _scrollRect.content.anchoredPosition.x,
        _scrollRect.content.anchoredPosition.y,
        _rectTransform.rect.width,
        _rectTransform.rect.height);

    // 스크롤 위치에 따른 시작 인덱스 계산
    var startIndex = Mathf.FloorToInt(visibleRect.y / cellHeight);

    // 화면에 보이게 될 Cell 개수 계산
    int visibleCount = Mathf.CeilToInt(visibleRect.height / cellHeight);

    // 버퍼 추가
    startIndex = Mathf.Max(0, startIndex - 1);      // startIndex가 0보다 크면 startIndex - 1, 아니면 0
    visibleCount += 2;

    return (startIndex, startIndex + visibleCount - 1);
}

 

그런데 이부분에서 VisbleCount를 잘못 구해서 너무 크게 구한 것이 문제였다. 

(        int visibleCount = Mathf.CeilToInt((visibleRect.y + visibleRect.height) / cellHeight); 로 구했었음)

 

 


문제를 해결하고, 다음으로는 만들고 있던 퀴즈 게임에 적용시켜보려고 하였다.

더 생각해야 하는 점은, 위의 경우 한 줄에 Cell이 하나였지만 퀴즈 게임은 3개라는 점이다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class StagePopUpPanelController2 : MonoBehaviour
{
   [SerializeField] private RectTransform viewPortRect;
   [SerializeField] private ScrollRect scrollRect;

   [SerializeField] private GameObject cellPrefab;

   [SerializeField] private float maxCellCount = 100;
   
   private List<GameObject> cells = new List<GameObject>();
   private LinkedList<GameObject> visibleCells;

   private float lastYValue = 1f;

   private float cellWidth;
   private float cellHeight;

   private float spacingX;
   private float spacingY;

   private int lastIndex = 20;
   private int firstIndex = 0;
   

   private void Awake()
   {
      scrollRect = GetComponent<ScrollRect>();
      viewPortRect = GetComponent<RectTransform>();
      GetComponent<PopupPanelController>().SetTitleText("STAGE");
      cellWidth = scrollRect.content.gameObject.GetComponent<GridLayoutGroup>().cellSize.x;
      cellHeight = scrollRect.content.gameObject.GetComponent<GridLayoutGroup>().cellSize.y;
      spacingX = scrollRect.content.gameObject.GetComponent<GridLayoutGroup>().spacing.x;
      spacingY = scrollRect.content.gameObject.GetComponent<GridLayoutGroup>().spacing.y;
   }

   private void LoadData()
   {
      for (int i = 0; i < 20; i++)
      {
         var stageCellButton = new GameObject();
         stageCellButton.AddComponent<StageCellButton>();
         stageCellButton.GetComponent<StageCellButton>().SetStageCell(
            i, StageCellButton.StageCellType.Lock);
         
      }
      
      ReloadData();
   }

   private void ReloadData()
   {
      visibleCells = new LinkedList<GameObject>();
      var contentSizeDelta = scrollRect.content.sizeDelta;
      contentSizeDelta.y = (cells.Count * cellHeight);
      scrollRect.content.sizeDelta = contentSizeDelta;


      var (startIndex, endIndex) = GetVisibleCellsIndex();
      startIndex *= 3;
      var maxEndIndex = Mathf.Min (endIndex*3, cells.Count-1);


      for (int i = startIndex; i < maxEndIndex; i++)
      {
         var cellObject = ObjectPool.Instance.GetObject();
         var stageCellButton = cellObject.GetComponent<StageCellButton>();
         stageCellButton.SetStageCell(lastIndex, StageCellButton.StageCellType.Lock);
         lastIndex++;
         visibleCells.AddFirst(cellObject);
      }

   }

   private (int startIndex, int endIndex) GetVisibleCellsIndex()
   {
      var visibleRect = new Rect(
         scrollRect.content.anchoredPosition.x,
         scrollRect.content.anchoredPosition.y,
         viewPortRect.rect.width,
         viewPortRect.rect.height);
      // 이제 인덱스 계산을 해야함 
      // 한 줄에 몇개 들어갈 수 있냐 ? 
      var oneLine =Mathf.FloorToInt((visibleRect.width) / (cellWidth + 60));

      var startIndex = Mathf.FloorToInt(visibleRect.y / (cellHeight)) / oneLine;
      
      int visibleCount = Mathf.CeilToInt((visibleRect.height)/cellHeight) / oneLine;
     
      //
      startIndex = Mathf.Max(0, startIndex - 1);      // startIndex가 0보다 크면 startIndex - 1, 아니면 0
      visibleCount += 2;

      return (startIndex, startIndex + visibleCount - 1);
   }

   private bool isVisible(int cellIndex)
   {
      var (startIndex, endIndex) = GetVisibleCellsIndex();
      endIndex = Mathf.Min(endIndex, cells.Count - 1);
      
      return startIndex <= cellIndex && cellIndex < endIndex;
   }

   private void OnValueChanged(Vector2 value)
   {
      if (lastYValue < value.y)
      {
         var firstCell = visibleCells.First;
         var newFirstCell = firstCell.
      }
   }
}

 

우리 코드에 적용시켜 보려고 했다가 중간에 뭔가 잘못된 것을 깨닫고 다시 한다. 

 

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class StagePopUpPanelController2 : MonoBehaviour
{
  [SerializeField] private float cellHeight;
  [SerializeField] private float cellWidth;
  
  [SerializeField] ScrollRect _scrollRect;
  [SerializeField] RectTransform _rectTransform;
  
  private List<StageCellData> _stageCells; // 갖고 있을 cell 정보들, 이걸 바탕으로 세팅
  private LinkedList<GameObject> _visibleCells; // 화면에 표시되고 있는 cell 정보

  private float _lastYValue = 1f;
  
  [SerializeField] private GameObject cellPrefab;

  private void Awake()
  {
    _scrollRect = GetComponent<ScrollRect>();
    _rectTransform = GetComponent<RectTransform>();
  }

  private void Start()
  {
    LoadData();
  }
  private void LoadData()
  {
    _stageCells = new List<StageCellData>();
    for (int i = 0; i < 100; i++)
    {
      var stageCell = new StageCellData()
      {
        stageIndex = i + 1, 

       stageCellType = i > 50  ? StageCellButton.StageCellType.Lock : StageCellButton.StageCellType.Clear
          
        }
        ;
      _stageCells.Add(stageCell);
    }

    ReloadData();
  }
/// <summary>
/// 데이터에 있는 값을 표시하는 함수
/// </summary>
  private void ReloadData()
  {
    //사이즈 조정
    _visibleCells = new LinkedList<GameObject>();
    var contentSizeDelta = _scrollRect.content.sizeDelta;
    contentSizeDelta.y = (_stageCells.Count) * cellHeight;
    _scrollRect.content.sizeDelta = contentSizeDelta;

    
    // 최초의 보여질 인덱스를 구함. 
    var (startIndex, endIndex) = GetVisibleIndexRange();
    // 마지막으로 보여질 index가 총 스테이지보다 크면 안되므로 조절해준다. 
    var maxIndex = Mathf.Min(endIndex, (_stageCells.Count - 1));

    
    // 한 줄에 들어갈 데이터를 로드한다 .
    for (int i = startIndex/3; i <= maxIndex/3; i++)
    {
      for (int j = 0; j < 3; j++)
      {
        Debug.Log(i);
        // var cellObj = ObjectPool.Instance.GetObject();
        // var stageCellButton = cellObj.GetComponent<StageCellButton>();
        // stageCellButton.SetStageCell(_stageCells[i].stageIndex,_stageCells[i].stageCellType);
        // _visibleCells.AddLast(cellObj);
        
        int stageIndex = i*3 + j;
        // 한 칸씩 더하다가 마지막 숫자보다 커지면 Countinue
        if (stageIndex >= _stageCells.Count) continue;

        var cellObject = ObjectPool.Instance.GetObject();
        var stageCellButton = cellObject.GetComponent<StageCellButton>();
        stageCellButton.SetStageCell(_stageCells[stageIndex].stageIndex, _stageCells[stageIndex].stageCellType);
        
        cellObject.transform.localPosition = new Vector3(190 + (340 *j), -i *(cellHeight+40), 0);
        _visibleCells.AddLast(cellObject);
      }
    }
  }


   //자 자 행 별로 어디까지 보일지 계산을 해 봅시다 .
  private (int startIndex, int endIndex) GetVisibleIndexRange()
  {
    var visibleRect = new Rect(
      _scrollRect.content.anchoredPosition.x,
      _scrollRect.content.anchoredPosition.y,
      _rectTransform.rect.width,
      _rectTransform.rect.height);
    
    //위치에 따른 인덱스 계산 
    // 현재 StartIndex는 그냥 앵커드 포지션을 높이로 나눈 것
    var startIndex = Mathf.FloorToInt(visibleRect.y/ cellHeight);
    int visibleCount = Mathf.CeilToInt(visibleRect.width/ cellHeight);
    
    //버퍼 추가
   // 여기서 3을 곱해주는 이유는 한 행에 3개가 들어가기 때문에 3배 큰 숫자까지 보이기 때문
   // Mathf로 비교를 해준 것은, 버퍼인, 즉 하나 위로도 미리 준비해야 함( 한 줄 위로 미리 준비 but 0일땐 안함)
    startIndex = Mathf.Max(0, startIndex - 1);
    // 다음 줄도 미리 준비
    visibleCount += 1;
    // return하는 것은 결국 몇 번째 인덱스부터 몇 번째 인덱스까지 보이게 할 것이냐는 사실
    return (startIndex * 3, (startIndex + visibleCount + 1)*3);
  }

  private bool isVisible(int index)
  {
    //index를 받아서 , 그 인덱스가 startIndex보다 크고, endIndex보다 작거나 같으면 참 값을 리턴(현재 기준)
    var (startIndex, endIndex) = GetVisibleIndexRange();
    return startIndex <= index && index <= endIndex;
  }

  //값이 들어왔을 때 
  public void OnValueChanged(Vector2 value)
  {
    Debug.Log("OnValueChanged");
    // 새로 들어온 값이 기존 값보다 클 때 , 즉 위로 움직이고 있을 때 
    if (_lastYValue < value.y)
    {
      //첫번째 셀
      var firstCell = _visibleCells.First;

      //첫번째 셀의 스테이지 index - 3  ( 그 전줄) 
      var newFirstCell = firstCell.Value.GetComponent<StageCellButton>().stageIndex - 3;

      // 그 전 줄이 보여야 하기 시작하면
      if (isVisible(newFirstCell))
      {
        // 그 전 줄을 구해줘야 한다. 
        for (int j = 0; j < 3; j++)
        {
          int cellIndex = newFirstCell + 2 -j;
          if (cellIndex < 0) continue;
          //오브젝트 풀에서 하나 가져와서
          var cell = ObjectPool.Instance.GetObject().GetComponent<StageCellButton>();
          // 맞는 cellIndex설정해주고 타입도 설정해준다.
          cell.SetStageCell(_stageCells[cellIndex].stageIndex, _stageCells[cellIndex].stageCellType);
          // 포지션은 정해진 위치( padding 고려)
          //cell.transform.localPosition = new Vector3(170 + (j * 170), -((cellIndex / 3) * cellHeight), 0);
          cell.gameObject.GetComponent<RectTransform>().anchoredPosition = new Vector3(190 + (340 *j), -(cellIndex/3)*(cellHeight+40), 0);
          // 이게 잘못된걸지도 ?
          _visibleCells.AddFirst(cell.gameObject);
        }
       //  var cell = ObjectPool.Instance.GetObject().GetComponent<StageCellButton>();
       //  cell.SetStageCell(newFirstCell, _stageCells[firstCellPosition-1].stageCellType);
       // if (firstCellPosition == 0) cell.transform.localPosition = new Vector3(170, (-newFirstCell * cellHeight)/3, 0);
       // else if(firstCellPosition == 1) cell.transform.localPosition = new Vector3(340, (-newFirstCell * cellHeight)/3, 0);
       // else if (firstCellPosition == 2) cell.transform.localPosition = new Vector3(510, (-newFirstCell * cellHeight)/3, 0);
       // _visibleCells.AddLast(cell.gameObject);
      }

      var lastCell = _visibleCells.Last;

      if (!isVisible(lastCell.Value.GetComponent<StageCellButton>().stageIndex))
      {
        // ObjectPool.Instance.ReturnObject(lastCell.Value);
        // _visibleCells.RemoveLast();
        for (int j = 0; j < 3; j++)
        {
          var cellToRemove = _visibleCells.Last.Value;
          ObjectPool.Instance.ReturnObject(cellToRemove);
          _visibleCells.RemoveLast();
        }
      }
    }
    else
    {
      // var lastCell = _visibleCells.Last;
      // var newLastCell = lastCell.Value.GetComponent<StageCellButton>().stageIndex + 1;
      // var newLastCellPosition = newLastCell % 3;
      //
      // if (isVisible(newLastCell))
      // {
      //   var cell = ObjectPool.Instance.GetObject().GetComponent<StageCellButton>();
      //   cell.SetStageCell(newLastCell, _stageCells[newLastCell].stageCellType);
      //   if (newLastCellPosition == 0) cell.transform.localPosition = new Vector3(170, (-newLastCell * cellHeight)/3, 0);
      //   else if(newLastCellPosition== 1) cell.transform.localPosition = new Vector3(340, (-newLastCell * cellHeight)/3, 0);
      //   else if (newLastCellPosition== 2) cell.transform.localPosition = new Vector3(510, (-newLastCell * cellHeight)/3, 0);
      //   _visibleCells.AddLast(cell.gameObject);
      // }
      //   var firstCell = _visibleCells.First;
      //   if (!isVisible(firstCell.Value.GetComponent<StageCellButton>().stageIndex))
      //   {
      //     ObjectPool.Instance.ReturnObject(firstCell.Value);
      //     _visibleCells.RemoveFirst();
      //   }
      
      var lastCell = _visibleCells.Last.Value;
      int newLastIndex = lastCell.GetComponent<StageCellButton>().stageIndex + 3;

      if (isVisible(newLastIndex))
      {
        for (int j = 0; j < 3; j++)
        {
          int cellIndex = newLastIndex + j;
          if (cellIndex >=_stageCells.Count) continue;
          
          var cell = ObjectPool.Instance.GetObject().GetComponent<StageCellButton>();
          cell.SetStageCell(cellIndex, _stageCells[cellIndex].stageCellType);
          cell.gameObject.GetComponent<RectTransform>().anchoredPosition = new Vector3(190 + (340 *j), -(cellIndex/3)*(cellHeight+40), 0);

          //cell.transform.localPosition = new Vector3(170 + (j * 170), -((cellIndex / 3) * cellHeight), 0);
          _visibleCells.AddLast(cell.gameObject);
        }
      }
      var firstCell = _visibleCells.First.Value;
      if (!isVisible(firstCell.GetComponent<StageCellButton>().stageIndex))
      {
        for (int j = 0; j < 3; j++)
        {
          var cellToRemove = _visibleCells.First.Value;
          ObjectPool.Instance.ReturnObject(cellToRemove);
          _visibleCells.RemoveFirst();
        }
      }
    }
    _lastYValue = value.y;
  }
}

 

중간 저장용

 

for (int j = 0; j < 3; j++)
{
  int cellIndex = newFirstCell + 2 -j;
  if (cellIndex < 0) continue;
  //오브젝트 풀에서 하나 가져와서
  var cell = ObjectPool.Instance.GetObject().GetComponent<StageCellButton>();
  // 맞는 cellIndex설정해주고 타입도 설정해준다.
  cell.SetStageCell(_stageCells[cellIndex].stageIndex, _stageCells[cellIndex].stageCellType);
  // 포지션은 정해진 위치( padding 고려)
  //cell.transform.localPosition = new Vector3(170 + (j * 170), -((cellIndex / 3) * cellHeight), 0);
  cell.gameObject.GetComponent<RectTransform>().anchoredPosition = new Vector3(190 + (340 *j), -(cellIndex/3)*(cellHeight+40), 0);
  // 이게 잘못된걸지도 ?
  _visibleCells.AddFirst(cell.gameObject);
}

 

이 짧은 코드에서만 고친 점이 두 가지 있다. 

 현재까지 해결한 문제

1. AddFirst의 순서가 잘못 적용된다. 

그냥 습관처럼 cellIndex =  newFirstCell + j를 해 버리면 앞에서부터 AddFirst를 순서대로 접근한다.

이는 맨 앞의 인덱스가 1, 그리고 2 ,3 이 나와야 하는데 잘못된 순서로 리스트에 넣어 3, 2, 1이라는 결과를 야기한다.

 

2. 위치 문제

Spacing과 Padding을 고려해 위치를 잡아주어야 한다, 캐싱해서 할 수도 있지만 지금 당장은 중요한 것이 아니라고 생각해 일단 하드코딩으로 했다.

 

 

 

해결해야 할 문제

잠깐 위로 올리거나, 위치를 바꾸면 OnValueChanged를 하면 계속해서 셀이 생성되는 문제가 있다. 인덱스는 99와 0  1  2로 ! 약 두 시간~ 세 시간 정도 이유를 못 찾아서 고민했다. 

 

두 가지 문제를 고쳤다. 

1. ViewPoint안에 있는지 검사하는 RectTransform을 조정했다. (인스펙터 창에서) 

2. 처음부터 끝까지 주석을 달며 , 디버깅을 하다보니 중간에 ; 를 찾았다.
처음 겪어보는 문제라... 내가 중간에 조건문을 잘못 검사했을 것이라고는 상상하지 못했다 ';'를 붙였기에 조건문을 검사하고 조건이 맞을 때만 실행하지 않고, 그냥 실행해 버렸다, { } 로 감싸져 있길래 당연히... 잘 작동되는 것을 보았다. 

 

 

주석을 달면서 다시 알게된 것 

 

 

더해서 Content의 길이자체도.. 잘못 조절함. 패딩을 고려하지 않았다. 

private (int startIndex, int endIndex) GetVisibleIndexRange()
{
    var visibleRect = new Rect(
        _scrollRect.content.anchoredPosition.x,
        _scrollRect.content.anchoredPosition.y,
        _rectTransform.rect.width,
        _rectTransform.rect.height);

    //위치에 따른 인덱스 계산 
    // 현재 StartIndex는 그냥 앵커드 포지션을 높이로 나눈 것
    var startIndex = Mathf.FloorToInt(visibleRect.y / cellHeight);
    int visibleCount = Mathf.CeilToInt(visibleRect.height / cellHeight);

    //버퍼 추가
    // 여기서 3을 곱해주는 이유는 한 행에 3개가 들어가기 때문에 3배 큰 숫자까지 보이기 때문
    // Mathf로 비교를 해준 것은, 버퍼인, 즉 하나 위로도 미리 준비해야 함( 한 줄 위로 미리 준비 but 0일땐 안함)
    startIndex = Mathf.Max(0, startIndex - 4);
    // 다음 줄도 미리 준비
    //visibleCount += 1;
    // return하는 것은 결국 몇 번째 인덱스부터 몇 번째 인덱스까지 보이게 할 것이냐는 사실
    return (startIndex * 3, (startIndex + visibleCount + 4 + 1) * 3);
}

return에서 startIndex + visibleCount + 4의 의미는, 현재 보여지고 있는 인덱스를 의미한다. 위에서 4를 빼줬기에 다시 더해줘야 하고, + 1 은 그 다음줄을 의미한다. 

강사님 코드에서 -2를 하고 + 1을 다시 하시길래 뭔가... 했었는데 이런 의미가 있었다. 

 

 

 

아직 남아 있는 문제

 

깜빡하고 동영상으로 남기는 것을 잊었다, 현재 밑으로 한 번 내려갈 때는 정상 동작하나

위로 한 번 올라갔다가 다시 내려가면 인덱스 하나를 빼 먹는 문제가 있다. 

 

이것도 두 시간 정도 고민하다가 다음과 같이 해결했다. 

else
{
  var lastCell = _visibleCells.Last.Value.GetComponent<StageCellButton>().stageIndex;
  int newLastIndex =  lastCell+ 1;
  newLastIndex = Mathf.Min(newLastIndex, (_stageCells.Count - 1));
  if (isVisible(newLastIndex) && lastCell != _stageCells.Count-1) 
  {
    var newLastCellHeight = (newLastIndex/3) * (cellHeight+40)+170;
    for (int j = 0; j < 3; j++)
    {
      int cellIndex = newLastIndex + j;
      if (cellIndex >=_stageCells.Count) continue;
      
      var cell = ObjectPool.Instance.GetObject().GetComponent<StageCellButton>();
      cell.SetStageCell(cellIndex, _stageCells[cellIndex].stageCellType);
      cell.gameObject.GetComponent<RectTransform>().anchoredPosition = new Vector3(190 + (340 *j), -newLastCellHeight, 0);
      _visibleCells.AddLast(cell.gameObject);
    }
  }

 

내가 마지막 셀을 결정하는 방법은, 한 줄이 삭제될 때마다 RemoveLast()를 세 번 호출하는 것이다. 

평상시에는 문제가 없지만, 마지막 줄이 3의 배수가 아니라면 ? 전체 데이터의 숫자 - 1 만큼까지만 새로운 데이터를 설정하고 이후에는 설정하지 않는데, 이 숫자가 3의 배수가 아니라면 우리는 세 개씩 삭제할 때 한 줄씩 삭제하는 것이 아니라

마지막 줄에 남아있는 것(1개 혹은 2개)를 삭제하고, 그 전 줄을 삭제하게 된다. 

이를 방지해주기 위해서는 마지막 줄을 따로 처리하던지, 데이터의 수를 맞춰줘야 한다. 

 

private void LoadData()
{
    _stageCells = new List<StageCellData>();


    // 데이터 사이즈가 3의 배수가 아니면 lastCell이 더 이상 쌓이지 않게 되고,
    // 3번씩 지우는 코드의 특성상 첫 번째 내려갈 때와 올라갔다가 다시 내려갈 때 다른 숫자를 세팅하게 된다.
    int originalDataSize = targetDataSize;
    targetDataSize = targetDataSize % 3 == 0 ? targetDataSize :
        (targetDataSize + 1) % 3 == 0 ? targetDataSize + 1 : targetDataSize + 2;
    for (int i = 0; i < targetDataSize; i++)
    {
        var stageCell = new StageCellData()
            {
                stageIndex = i,

                // stageCellType = i > 50  ? StageCellButton.StageCellType.Lock : StageCellButton.StageCellType.Clear
                stageCellType = StageCellButton.StageCellType.Clear,
            }
            ;
        if (i >= originalDataSize) stageCell.stageCellType = StageCellButton.StageCellType.Lock;

        _stageCells.Add(stageCell);
    }

    ReloadData();
}

 

따라서 데이터의 숫자를 맞춰주었다. 

나중에 새로운 타입을 하나 더 만들어서 아예 보이지 않게 해도 괜찮을 것 같다. 

 

 

 


그래도 어제는 새벽 두 시까지 했는데 오늘은 열 시 반쯤 끝났다, 혼자서는 6시간... 좀 넘게 시간이 소요되었다. 

이번에는 다른 분의 코드를 참고하지 않고, 배운 내용을 바탕으로 스스로 했다는 점, 그리고 버그가 많았지만 다 수정했다는 점 (아직 다른 버그가 있을수도 있지만)이 마음에 든다. 뿌듯한 하루 ! 

 

앞으로는 절대 세미콜론을 무시하지 않겠습니다.