[SKKU DT] 48일차 -유니티 공공API 데이터 활용으로 UI 만들기 -Queue를 이용한 셀의 Object Pool 만들기, Unity Version Control 사용하기

2024. 1. 9. 17:29SKKU DT

728x90
반응형
 

[SKKU DT] 47일차 -유니티 공공API 데이터 활용으로 UI 만들기 - CURD, 대리자(Delegate), LinkedList, WinMerge

CRUD는 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 묶어서 일컫는 말이다. Create 패널을 하나 만들어서 InputField로 사용자

lightbakery.tistory.com


이전 글에 이어서, 셀 목록에 Object Pool을 적용하는 것을 이어서 진행한다. -Self Study

이전 Cell과 CellPool 스크립트를 갈아엎고 LegacyDataManager에서 수정할 방법을 찾아본다.

GPT를 이용하려 했지만 잘 되지 않았다....

 

 


 

 

강사님의 답

상속 구조로 받아서 쓰게 만들예정이다.

GOTableViewController 스크립트 생성

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

public abstract class GOTableViewController : MonoBehaviour
{
    //Cell을 생성하기 위한 cell 프리팹
    [SerializeField] protected GOTableViewCell cellPrefab; //protected는 상속받은 클래스에서 사용 가능. 어차피 Serialized이기 때문에 public은 있으나 마나.
    //Cell을 생성하게 될 content 객체
    [SerializeField] protected Transform content;
    //Queue 생성
    protected Queue<GOTableViewCell> reuseQueue = new Queue<GOTableViewCell>();

    //cell의 높이
    private float cellHeight;
    //전체 셀의 개수
    private int totalRows;

    //자식 클래스에서 GOTableViewController가 표시해야 할 전체 Data의 수를 반환하는 메서드
    protected abstract int numberOfRows(); //추상 메서드 생성, 맨 위의 class도 추상 클래스로 변경(public abstract class GOTableViewController) 자식 클래스가 상속받아서 해당 부분을 채울 것이다. 틀만 만드는 느낌.
                                           //스크롤 바가 보이는 부분에다 맞추면 안되고 전체의 셀에 대한 스크롤 크기를 맞춰야 한다.
    protected abstract GOTableViewCell cellForRowAtIndex(int index); //추상 메서드 하나 더 생성.

    protected virtual void Start() //virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.
    {
        totalRows = this.numberOfRows();

        float screenHeight = Screen.height; //화면의 높이 얻어오기. Canvas의 높이가 960으로 설정되어있다.

        cellHeight = cellPrefab.GetComponent<RectTransform>().sizeDelta.y; //셀의 높이. 200으로 설정되어있다.

        //표시해야 할 cell의 수 계산
        int maxRows = (int)screenHeight / (int)cellHeight + 2; //줄 수이기 때문에 int로 줄의 개수를 자연수로 받는다. 한 화면에 표시되는 셀의 개수는 960/200=4.8정도 된다. 아래 가려진 셀의 개수에 여유를 주기 위해 2를 더한다.
        maxRows = (maxRows > totalRows) ? totalRows : maxRows;

        //전체 셀을 포함하는 높이를 가질 수 도록 content의 크기를 조정
        RectTransform contentTransform = content.GetComponent<RectTransform>();
        contentTransform.sizeDelta = new Vector2(0, totalRows * cellHeight);
    }
}

virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.

 

 


 

 

GOTableViewController  스크립트를 상속받는 자식 클래스 LegacyTableViewController 스크립트 생성

public class LegacyTableViewController : GOTableViewController
{

}

GOTableViewController를 상속받는다.

 

 

클래스 이름에 오류 표시가 뜨는데, Alt+Enter로 자동 수정을 불러오고 적용하면 추상 메서드를 가져올 수 있다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyTableViewController : GOTableViewController
{

    [SerializeField] private string serviceKey; //서버 키 입력
    private List<Legacy> legacyList = new List<Legacy>(); //리스트 생성

    protected void Start()
    {
        StartCoroutine(LoadData());
    }

    protected override int numberOfRows()
    {
        return legacyList.Count; //개수 반환
    }
    protected override GOTableViewCell cellForRowAtIndex(int index)
    {
        return null;//특정한 index의 셀을 만들려고할 때 특정한 셀을 만들어서 반환한다.
    }

    //이전 LegacyDataManger에서 LoadData 부분을 가져온다. 더보기 버튼 관련 코드는 지운다.
    IEnumerator LoadData()
    {
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, 0, 100, serviceKey);

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for (int i = 0; i < legacyArray.Length; i++)
                {
                    legacyList.Add(legacyArray[i]);
                }
            }

            base.Start();
        }
    }
}

 

 

Content에 Vertical Layout Group과 Content Size Fitter 지우기. 스크립트로 조절할 예정

 

 

LegacyCell 프리팹을 복사해서 LegacyTableViewCell 만들기

기존에 들어있던 스크립트 지우고 LEgacyTableViewCell 추가

 

 

CellPrefab 자리에 LegacyTableViewCell 끌어다 넣기

 

서비스 키 넣기

 

Content의 높이가 셀의 개수에 맞게 엄청 길어져있으면 된다.

 

 


 

 

GOTableViewCell  스크립트를 상속받는 자식 클래스 LegacyTableViewCell 스크립트 생성

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

public class LegacyTableViewCell : GOTableViewCell
{
    [SerializeField] TMP_Text legacyName;
    [SerializeField] TMP_Text type;
    [SerializeField] TMP_Text designatedDate;
}

 

 


 

 

GOTableViewController 스크립트 수정

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

public abstract class GOTableViewController : MonoBehaviour
{
    //Cell을 생성하기 위한 cell 프리팹
    [SerializeField] protected GOTableViewCell cellPrefab; //protected는 상속받은 클래스에서 사용 가능. 어차피 Serialized이기 때문에 public은 있으나 마나.
    //Cell을 생성하게 될 content 객체
    [SerializeField] protected Transform content;
    //Queue 생성
    protected Queue<GOTableViewCell> reuseQueue = new Queue<GOTableViewCell>();

    //cell의 높이
    private float cellHeight;
    //전체 셀의 개수
    private int totalRows;

    //자식 클래스에서 GOTableViewController가 표시해야 할 전체 Data의 수를 반환하는 메서드
    protected abstract int numberOfRows(); //추상 메서드 생성, 맨 위의 class도 추상 클래스로 변경(public abstract class GOTableViewController) 자식 클래스가 상속받아서 해당 부분을 채울 것이다. 틀만 만드는 느낌.
                                           //스크롤 바가 보이는 부분에다 맞추면 안되고 전체의 셀에 대한 스크롤 크기를 맞춰야 한다.
    protected abstract GOTableViewCell cellForRowAtIndex(int index); //추상 메서드 하나 더 생성.

    protected virtual void Start() //virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.
    {
        totalRows = this.numberOfRows();

        float screenHeight = Screen.height; //화면의 높이 얻어오기. Canvas의 높이가 960으로 설정되어있다.

        cellHeight = cellPrefab.GetComponent<RectTransform>().sizeDelta.y; //셀의 높이. 200으로 설정되어있다.

        //표시해야 할 cell의 수 계산
        int maxRows = (int)screenHeight / (int)cellHeight + 2; //줄 수이기 때문에 int로 줄의 개수를 자연수로 받는다. 한 화면에 표시되는 셀의 개수는 960/200=4.8정도 된다. 아래 가려진 셀의 개수에 여유를 주기 위해 2를 더한다.
        maxRows = (maxRows > totalRows) ? totalRows : maxRows;

        //전체 셀을 포함하는 높이를 가질 수 도록 content의 크기를 조정
        RectTransform contentTransform = content.GetComponent<RectTransform>();
        contentTransform.sizeDelta = new Vector2(0, totalRows * cellHeight);
    }

    protected GOTableViewCell dequeueReuseableCell() //재사용이 가능한 Cell을 반환해주는 메서드. 재사용 cell 혹은 mull이 반환된다.
    {
        if(reuseQueue.Count > 0) //쓸만한 셀이 있다면
        {
            GOTableViewCell cell = reuseQueue.Dequeue();
            cell.gameObject.SetActive(true);
            return cell;
        }
        else
        {
            return null; //재사용 가능한 셀이 있다면 Instantiate 할 수 있다고 생각할 수 있는데, 여기서 자식 형태의 cell을 만들어낼 수는 없다. 자식 쪽에서 만들어야 함. 넣었던 자식이 그대로 반환될 것.
        }
    }
}

 

 

LegacyTableViewController 수정

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyTableViewController : GOTableViewController
{

    [SerializeField] private string serviceKey; //서버 키 입력
    private List<Legacy> legacyList = new List<Legacy>(); //리스트 생성

    protected void Start()
    {
        StartCoroutine(LoadData());
    }

    protected override int numberOfRows()
    {
        return legacyList.Count; //개수 반환
    }
    protected override GOTableViewCell cellForRowAtIndex(int index)
    {
        //특정한 index의 셀을 만들려고할 때 특정한 셀을 만들어서 반환한다.
        //부모에 만들어 놓으면, 상속만 받으면 가져다 쓰기만 하면 된다. 셀을 생성 하는 건 자식이 하더라도 셀을 재사용할 수 있는 로직은 GOTableViewController에서 만든다.
        LegacyTableViewCell cell = dequeueReuseableCell() as LegacyTableViewCell; //재사용 가능한 셀이 있는가 as로 변환을 시킨다. 메서드의 반환 값이 GOTableViewCell이다.

        if(cell == null)
        {
            cell = Instantiate(cellPrefab, content) as LegacyTableViewCell; //변환
        }

        return null;
    }

    //이전 LegacyDataManger에서 LoadData 부분을 가져온다. 더보기 버튼 관련 코드는 지운다.
    IEnumerator LoadData()
    {
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, 0, 100, serviceKey);

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for (int i = 0; i < legacyArray.Length; i++)
                {
                    legacyList.Add(legacyArray[i]);
                }
            }

            base.Start();
        }
    }
}

 

 

LegacyTableViewCell 스크립트 수정 -public으로 수정. 그래야 LegacyTableViewController에서 cell.~~을 쓸 수 있다.

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

public class LegacyTableViewCell : GOTableViewCell
{
    public TMP_Text legacyName;
    public TMP_Text type;
    public TMP_Text designatedDate;
}

 

 

LegacyTableViewController 스크립트 수정

cell.legacyName.text = legacyList[index].명칭;
cell.type.text = legacyList[index].종목;
cell.designatedDate.text = legacyList[index].지정일;

위의 내용 추가, 아래 전체 코드

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyTableViewController : GOTableViewController
{

    [SerializeField] private string serviceKey; //서버 키 입력
    private List<Legacy> legacyList = new List<Legacy>(); //리스트 생성

    protected void Start()
    {
        StartCoroutine(LoadData());
    }

    protected override int numberOfRows()
    {
        return legacyList.Count; //개수 반환
    }
    protected override GOTableViewCell cellForRowAtIndex(int index)
    {
        //특정한 index의 셀을 만들려고할 때 특정한 셀을 만들어서 반환한다.
        //부모에 만들어 놓으면, 상속만 받으면 가져다 쓰기만 하면 된다. 셀을 생성 하는 건 자식이 하더라도 셀을 재사용할 수 있는 로직은 GOTableViewController에서 만든다.
        LegacyTableViewCell cell = dequeueReuseableCell() as LegacyTableViewCell; //재사용 가능한 셀이 있는가 as로 변환을 시킨다. 메서드의 반환 값이 GOTableViewCell이다.

        if(cell == null)
        {
            cell = Instantiate(cellPrefab, content) as LegacyTableViewCell; //변환
        }

        cell.legacyName.text = legacyList[index].명칭;
        cell.type.text = legacyList[index].종목;
        cell.designatedDate.text = legacyList[index].지정일;

        return cell;
    }

    //이전 LegacyDataManger에서 LoadData 부분을 가져온다. 더보기 버튼 관련 코드는 지운다.
    IEnumerator LoadData()
    {
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, 0, 100, serviceKey);

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for (int i = 0; i < legacyArray.Length; i++)
                {
                    legacyList.Add(legacyArray[i]);
                }
            }

            base.Start();
        }
    }
}

아직 재사용 가능한 셀을 Queue에 넣는 작업은 하지 않았다.

 

 

GOTableViewController 스크립트 수정 (수정 내용은 WinMerge로 이전 것과 비교하면 편하다)

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

public abstract class GOTableViewController : MonoBehaviour
{
    //Cell을 생성하기 위한 cell 프리팹
    [SerializeField] protected GOTableViewCell cellPrefab; //protected는 상속받은 클래스에서 사용 가능. 어차피 Serialized이기 때문에 public은 있으나 마나.
    //Cell을 생성하게 될 content 객체
    [SerializeField] protected Transform content;
    //Queue 생성
    protected Queue<GOTableViewCell> reuseQueue = new Queue<GOTableViewCell>();

    //cell의 높이
    private float cellHeight;
    //전체 셀의 개수
    private int totalRows;

    //자식 클래스에서 GOTableViewController가 표시해야 할 전체 Data의 수를 반환하는 메서드
    protected abstract int numberOfRows(); //추상 메서드 생성, 맨 위의 class도 추상 클래스로 변경(public abstract class GOTableViewController) 자식 클래스가 상속받아서 해당 부분을 채울 것이다. 틀만 만드는 느낌.
                                           //스크롤 바가 보이는 부분에다 맞추면 안되고 전체의 셀에 대한 스크롤 크기를 맞춰야 한다.
    protected abstract GOTableViewCell cellForRowAtIndex(int index); //추상 메서드 하나 더 생성.

    protected virtual void Start() //virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.
    {
        totalRows = this.numberOfRows(); //전체 몇 개의 값이 필요한지 얻어온다.

        float screenHeight = Screen.height; //화면의 높이 얻어오기. Canvas의 높이가 960으로 설정되어있다.

        cellHeight = cellPrefab.GetComponent<RectTransform>().sizeDelta.y; //셀의 높이. 200으로 설정되어있다.

        //표시해야 할 cell의 수 계산
        int maxRows = (int)screenHeight / (int)cellHeight + 2; //줄 수이기 때문에 int로 줄의 개수를 자연수로 받는다. 한 화면에 표시되는 셀의 개수는 960/200=4.8정도 된다. 아래 가려진 셀의 개수에 여유를 주기 위해 2를 더한다.
        maxRows = (maxRows > totalRows) ? totalRows : maxRows;

        //전체 셀을 포함하는 높이를 가질 수 도록 content의 크기를 조정
        RectTransform contentTransform = content.GetComponent<RectTransform>();
        contentTransform.sizeDelta = new Vector2(0, totalRows * cellHeight);

        //초기 Cell을 생성
        for(int i = 0; i < maxRows; i++)
        {
            GOTableViewCell cell = cellForRowAtIndex(i);//인덱스만 넣으면 셀이 만들어지도록.
            cell.gameObject.transform.localPosition = new Vector3(0, -i * cellHeight, 0); //첫 번째 셀 높이는 0, 두 번째 셀 높이는 -1 * 200 = -200
        }
    }

    protected GOTableViewCell dequeueReuseableCell() //재사용이 가능한 Cell을 반환해주는 메서드. 재사용 cell 혹은 null이 반환된다.
    {
        if(reuseQueue.Count > 0) //쓸만한 셀이 있다면
        {
            GOTableViewCell cell = reuseQueue.Dequeue();
            cell.gameObject.SetActive(true);
            return cell;
        }
        else
        {
            return null; //재사용 가능한 셀이 있다면 Instantiate 할 수 있다고 생각할 수 있는데, 여기서 자식 형태의 cell을 만들어낼 수는 없다. 자식 쪽에서 만들어야 함. 넣었던 자식이 그대로 반환될 것.
        }
    }
}

아직 잘 나오지는 않는다. Content의 피봇이 왼쪽에 있다...

피봇 x 값을 0.5로 설정하니 잘 나온다.

 

 


 

 

Scroll View의 OnValueChange에 넣을 함수를 GOTableViewController에 아래에 만든다.

public void OnValueChanged(Vector2 vector)
{
    Debug.Log(vector);
}

가장 아래로 내려가면 x, y가 0이 된다.

 

 

다른 값을 Debug.Log 하면 아래와 같다.

public void OnValueChanged(Vector2 vector)
{
    Debug.Log($"Screen.height: {Screen.height}, Content.localPosition.y: {content.localPosition.y}");

 

 


 

 

이제 재사용 가능한 셀을 만들어야 한다. First Cell과 Last Cell의 상태를 보고 추가할지 지울지를 판단한다. LinkedList가 필요한 상황.

GOTableViewController 스크립트 수정 -LinkedList<> 생성

//Cell 간의 연결고리를 관리하기 위한 List
protected LinkedList<GOTableViewCell> cellLinkedList = new LinkedList<GOTableViewCell>();
//초기 Cell을 생성
for(int i = 0; i < maxRows; i++)
{
    GOTableViewCell cell = cellForRowAtIndex(i);//인덱스만 넣으면 셀이 만들어지도록.
    cell.gameObject.transform.localPosition = new Vector3(0, -i * cellHeight, 0); //첫 번째 셀 높이는 0, 두 번째 셀 높이는 -1 * 200 = -200
    //LinkedList에 Last Cell로 추가
    cellLinkedList.AddLast(cell);
}

LinkedList를 생성하고 AddLast로 추가된다.

전체 코

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

public abstract class GOTableViewController : MonoBehaviour
{
    //Cell을 생성하기 위한 cell 프리팹
    [SerializeField] protected GOTableViewCell cellPrefab; //protected는 상속받은 클래스에서 사용 가능. 어차피 Serialized이기 때문에 public은 있으나 마나.
    //Cell을 생성하게 될 content 객체
    [SerializeField] protected Transform content;
    //Cell 재사용을 위한 Queue 생성
    protected Queue<GOTableViewCell> reuseQueue = new Queue<GOTableViewCell>();
    //Cell 간의 연결고리를 관리하기 위한 List
    protected LinkedList<GOTableViewCell> cellLinkedList = new LinkedList<GOTableViewCell>();

    //cell의 높이
    private float cellHeight;
    //전체 셀의 개수
    private int totalRows;

    //자식 클래스에서 GOTableViewController가 표시해야 할 전체 Data의 수를 반환하는 메서드
    protected abstract int numberOfRows(); //추상 메서드 생성, 맨 위의 class도 추상 클래스로 변경(public abstract class GOTableViewController) 자식 클래스가 상속받아서 해당 부분을 채울 것이다. 틀만 만드는 느낌.
                                           //스크롤 바가 보이는 부분에다 맞추면 안되고 전체의 셀에 대한 스크롤 크기를 맞춰야 한다.
    protected abstract GOTableViewCell cellForRowAtIndex(int index); //추상 메서드 하나 더 생성.

    protected virtual void Start() //virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.
    {
        totalRows = this.numberOfRows(); //전체 몇 개의 값이 필요한지 얻어온다.

        float screenHeight = Screen.height; //화면의 높이 얻어오기. Canvas의 높이가 960으로 설정되어있다.

        cellHeight = cellPrefab.GetComponent<RectTransform>().sizeDelta.y; //셀의 높이. 200으로 설정되어있다.

        //표시해야 할 cell의 수 계산
        int maxRows = (int)screenHeight / (int)cellHeight + 2; //줄 수이기 때문에 int로 줄의 개수를 자연수로 받는다. 한 화면에 표시되는 셀의 개수는 960/200=4.8정도 된다. 아래 가려진 셀의 개수에 여유를 주기 위해 2를 더한다.
        maxRows = (maxRows > totalRows) ? totalRows : maxRows;

        //전체 셀을 포함하는 높이를 가질 수 도록 content의 크기를 조정
        RectTransform contentTransform = content.GetComponent<RectTransform>();
        contentTransform.sizeDelta = new Vector2(0, totalRows * cellHeight);

        //초기 Cell을 생성
        for(int i = 0; i < maxRows; i++)
        {
            GOTableViewCell cell = cellForRowAtIndex(i);//인덱스만 넣으면 셀이 만들어지도록.
            cell.gameObject.transform.localPosition = new Vector3(0, -i * cellHeight, 0); //첫 번째 셀 높이는 0, 두 번째 셀 높이는 -1 * 200 = -200
            //LinkedList에 Last Cell로 추가
            cellLinkedList.AddLast(cell);
        }
    }

    protected GOTableViewCell dequeueReuseableCell() //재사용이 가능한 Cell을 반환해주는 메서드. 재사용 cell 혹은 null이 반환된다.
    {
        if(reuseQueue.Count > 0) //쓸만한 셀이 있다면
        {
            GOTableViewCell cell = reuseQueue.Dequeue();
            cell.gameObject.SetActive(true);
            return cell;
        }
        else
        {
            return null; //재사용 가능한 셀이 있다면 Instantiate 할 수 있다고 생각할 수 있는데, 여기서 자식 형태의 cell을 만들어낼 수는 없다. 자식 쪽에서 만들어야 함. 넣었던 자식이 그대로 반환될 것.
        }
    }

    public void OnValueChanged(Vector2 vector)
    {
        Debug.Log($"Screen.height: {Screen.height}, Content.localPosition.y: {content.localPosition.y}");
    }
}

 

 

GOTableViewCell 스크립트 생성

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

public class GOTableViewCell : MonoBehaviour
{
    public int Index;
}

Index가 추가되었다.

 

 

GOTableViewController 스크립트 수정 -OnValueChanged 함수 작성

public void OnValueChanged(Vector2 vector)
{
    Debug.Log($"Screen.height: {Screen.height}, Content.localPosition.y: {content.localPosition.y}");

    if (content.localPosition.y + Screen.height > cellLinkedList.Last.Value.Index * cellHeight + cellHeight) // 부등호 왼쪽은 현재 위치 + 스크린 높이, 부등호 오른쪽은 마지막 보이는 셀의 개수 * 셀의 높이 + 여유 높이 하나 추가. 스크롤을 내려서 스크린 높이가 커지면,
    {
        //하단에 새로운 Cell이 만들어지는 상황
        //처음에 있던 Cell은 Reuse Queue에 저장
        LinkedListNode<GOTableViewCell> firstCellNode = cellLinkedList.First; //첫 번째 셀은 이미 화면을 벗어나서 없을 테니까.
        cellLinkedList.RemoveFirst(); //첫 번째 있던 값을 지워버리기
        firstCellNode.Value.gameObject.SetActive(false); //첫 번째 있던 셀을 비활성화하기
        reuseQueue.Enqueue(firstCellNode.Value); //reuseQueue에 저장하기

        //하단에 새로운 Cell 생성
        LinkedListNode<GOTableViewCell> lastCellNode = cellLinkedList.Last;
        int currentIndex = lastCellNode.Value.Index;
        GOTableViewCell cell = cellForRowAtIndex(currentIndex + 1);
        cell.gameObject.transform.localPosition = new Vector3(0, -(currentIndex + 1) * cellHeight, 0); //좌표를 마지막으로 보낸다.
        cellLinkedList.AddAfter(lastCellNode, cell);
        cell.gameObject.transform.SetAsLastSibling(); //게임 오브젝트 순서도 마지막으로 보낸다.
    }
    else if (content.localPosition.y < cellLinkedList.First.Value.Index * cellHeight)
    {
        //상단에 새로운 Cell이 만들어지는 상황
        //마지막에 있던 Cell은 Reuse Queue에 저장
        LinkedListNode<GOTableViewCell> lastCellNode = cellLinkedList.Last;
        cellLinkedList.RemoveLast();
        lastCellNode.Value.gameObject.SetActive(false);
        reuseQueue.Enqueue(lastCellNode.Value);

        //상단에 새로운 Cell 생성
        LinkedListNode<GOTableViewCell> firstCellNode = cellLinkedList.First;
        int currentIndex = firstCellNode.Value.Index;
        GOTableViewCell cell = cellForRowAtIndex(currentIndex - 1);
        cell.gameObject.transform.localPosition = new Vector3(0, -(currentIndex - 1) * cellHeight, 0);
        cellLinkedList.AddBefore(firstCellNode, cell);
        cell.gameObject.transform.SetAsFirstSibling();
    }
}

 

전체 코드

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

public abstract class GOTableViewController : MonoBehaviour
{
    //Cell을 생성하기 위한 cell 프리팹
    [SerializeField] protected GOTableViewCell cellPrefab; //protected는 상속받은 클래스에서 사용 가능. 어차피 Serialized이기 때문에 public은 있으나 마나.
    //Cell을 생성하게 될 content 객체
    [SerializeField] protected Transform content;
    //Cell 재사용을 위한 Queue 생성
    protected Queue<GOTableViewCell> reuseQueue = new Queue<GOTableViewCell>();
    //Cell 간의 연결고리를 관리하기 위한 List
    protected LinkedList<GOTableViewCell> cellLinkedList = new LinkedList<GOTableViewCell>();

    //cell의 높이
    private float cellHeight;
    //전체 셀의 개수
    private int totalRows;

    //자식 클래스에서 GOTableViewController가 표시해야 할 전체 Data의 수를 반환하는 메서드
    protected abstract int numberOfRows(); //추상 메서드 생성, 맨 위의 class도 추상 클래스로 변경(public abstract class GOTableViewController) 자식 클래스가 상속받아서 해당 부분을 채울 것이다. 틀만 만드는 느낌.
                                           //스크롤 바가 보이는 부분에다 맞추면 안되고 전체의 셀에 대한 스크롤 크기를 맞춰야 한다.
    protected abstract GOTableViewCell cellForRowAtIndex(int index); //추상 메서드 하나 더 생성.

    protected virtual void Start() //virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.
    {
        totalRows = this.numberOfRows(); //전체 몇 개의 값이 필요한지 얻어온다.

        float screenHeight = Screen.height; //화면의 높이 얻어오기. Canvas의 높이가 960으로 설정되어있다.

        cellHeight = cellPrefab.GetComponent<RectTransform>().sizeDelta.y; //셀의 높이. 200으로 설정되어있다.

        //표시해야 할 cell의 수 계산
        int maxRows = (int)screenHeight / (int)cellHeight + 2; //줄 수이기 때문에 int로 줄의 개수를 자연수로 받는다. 한 화면에 표시되는 셀의 개수는 960/200=4.8정도 된다. 아래 가려진 셀의 개수에 여유를 주기 위해 2를 더한다.
        maxRows = (maxRows > totalRows) ? totalRows : maxRows;

        //전체 셀을 포함하는 높이를 가질 수 도록 content의 크기를 조정
        RectTransform contentTransform = content.GetComponent<RectTransform>();
        contentTransform.sizeDelta = new Vector2(0, totalRows * cellHeight);

        //초기 Cell을 생성
        for(int i = 0; i < maxRows; i++)
        {
            GOTableViewCell cell = cellForRowAtIndex(i);//인덱스만 넣으면 셀이 만들어지도록.
            cell.gameObject.transform.localPosition = new Vector3(0, -i * cellHeight, 0); //첫 번째 셀 높이는 0, 두 번째 셀 높이는 -1 * 200 = -200
            //LinkedList에 Last Cell로 추가
            cellLinkedList.AddLast(cell);
        }
    }

    protected GOTableViewCell dequeueReuseableCell() //재사용이 가능한 Cell을 반환해주는 메서드. 재사용 cell 혹은 null이 반환된다.
    {
        if(reuseQueue.Count > 0) //쓸만한 셀이 있다면
        {
            GOTableViewCell cell = reuseQueue.Dequeue();
            cell.gameObject.SetActive(true);
            return cell;
        }
        else
        {
            return null; //재사용 가능한 셀이 있다면 Instantiate 할 수 있다고 생각할 수 있는데, 여기서 자식 형태의 cell을 만들어낼 수는 없다. 자식 쪽에서 만들어야 함. 넣었던 자식이 그대로 반환될 것.
        }
    }

    public void OnValueChanged(Vector2 vector)
    {
        Debug.Log($"Screen.height: {Screen.height}, Content.localPosition.y: {content.localPosition.y}");

        if (content.localPosition.y + Screen.height > cellLinkedList.Last.Value.Index * cellHeight + cellHeight) // 부등호 왼쪽은 현재 위치 + 스크린 높이, 부등호 오른쪽은 마지막 보이는 셀의 개수 * 셀의 높이 + 여유 높이 하나 추가. 스크롤을 내려서 스크린 높이가 커지면,
        {
            //하단에 새로운 Cell이 만들어지는 상황
            //처음에 있던 Cell은 Reuse Queue에 저장
            LinkedListNode<GOTableViewCell> firstCellNode = cellLinkedList.First; //첫 번째 셀은 이미 화면을 벗어나서 없을 테니까.
            cellLinkedList.RemoveFirst(); //첫 번째 있던 값을 지워버리기
            firstCellNode.Value.gameObject.SetActive(false); //첫 번째 있던 셀을 비활성화하기
            reuseQueue.Enqueue(firstCellNode.Value); //reuseQueue에 저장하기

            //하단에 새로운 Cell 생성
            LinkedListNode<GOTableViewCell> lastCellNode = cellLinkedList.Last;
            int currentIndex = lastCellNode.Value.Index;
            GOTableViewCell cell = cellForRowAtIndex(currentIndex + 1);
            cell.gameObject.transform.localPosition = new Vector3(0, -(currentIndex + 1) * cellHeight, 0); //좌표를 마지막으로 보낸다.
            cellLinkedList.AddAfter(lastCellNode, cell);
            cell.gameObject.transform.SetAsLastSibling(); //게임 오브젝트 순서도 마지막으로 보낸다.
        }
        else if (content.localPosition.y < cellLinkedList.First.Value.Index * cellHeight)
        {
            //상단에 새로운 Cell이 만들어지는 상황
            //마지막에 있던 Cell은 Reuse Queue에 저장
            LinkedListNode<GOTableViewCell> lastCellNode = cellLinkedList.Last;
            cellLinkedList.RemoveLast();
            lastCellNode.Value.gameObject.SetActive(false);
            reuseQueue.Enqueue(lastCellNode.Value);

            //상단에 새로운 Cell 생성
            LinkedListNode<GOTableViewCell> firstCellNode = cellLinkedList.First;
            int currentIndex = firstCellNode.Value.Index;
            GOTableViewCell cell = cellForRowAtIndex(currentIndex - 1);
            cell.gameObject.transform.localPosition = new Vector3(0, -(currentIndex - 1) * cellHeight, 0);
            cellLinkedList.AddBefore(firstCellNode, cell);
            cell.gameObject.transform.SetAsFirstSibling();
        }
    }
}

아직 오류가 많다.

 

 


 

 

LegacyTableViewController 스크립트에 한 줄 추가

cell.Index = index;

 

전체 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class LegacyTableViewController : GOTableViewController
{

    [SerializeField] private string serviceKey; //서버 키 입력
    private List<Legacy> legacyList = new List<Legacy>(); //리스트 생성

    protected void Start()
    {
        StartCoroutine(LoadData());
    }

    protected override int numberOfRows()
    {
        return legacyList.Count; //개수 반환
    }
    protected override GOTableViewCell cellForRowAtIndex(int index)
    {
        //특정한 index의 셀을 만들려고할 때 특정한 셀을 만들어서 반환한다.
        //부모에 만들어 놓으면, 상속만 받으면 가져다 쓰기만 하면 된다. 셀을 생성 하는 건 자식이 하더라도 셀을 재사용할 수 있는 로직은 GOTableViewController에서 만든다.
        LegacyTableViewCell cell = dequeueReuseableCell() as LegacyTableViewCell; //재사용 가능한 셀이 있는가 as로 변환을 시킨다. 메서드의 반환 값이 GOTableViewCell이다.

        if(cell == null)
        {
            cell = Instantiate(cellPrefab, content) as LegacyTableViewCell; //변환
        }

        cell.Index = index;
        cell.legacyName.text = legacyList[index].명칭;
        cell.type.text = legacyList[index].종목;
        cell.designatedDate.text = legacyList[index].지정일;

        return cell;
    }

    //이전 LegacyDataManger에서 LoadData 부분을 가져온다. 더보기 버튼 관련 코드는 지운다.
    IEnumerator LoadData()
    {
        string url = string.Format("{0}?page={1}&perPage={2}&serviceKey={3}", Constants.url, 0, 100, serviceKey);

        UnityWebRequest request = new UnityWebRequest();
        using (request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if (request.result != UnityWebRequest.Result.Success)
            {
                Debug.Log(request.error);
            }
            else
            {
                string result = request.downloadHandler.text; //텍스트 형식으로 받아오기. 많이 불러왔었던 빼곡한 글들로 불러온다.

                LegacyData legacyData = JsonUtility.FromJson<LegacyData>(result); //파싱에서 가져온 이름 LegacyData
                Legacy[] legacyArray = legacyData.data;

                for (int i = 0; i < legacyArray.Length; i++)
                {
                    legacyList.Add(legacyArray[i]);
                }
            }

            base.Start();
        }
    }
}

 

내려갈때 계속 셀이 생성되어 잘 작동한다. 하지만 맨위/맨끝으로 가면 오류가 난다.

 

 


 

 

끝단에 가면 동작하지 않고 하나 전까지만 동작하게 조건을 추가한다.

GOTableViewController 스크립트 수정

if ((cellLinkedList.Last.Value.Index < totalRows - 1) && (content.localPosition.y + Screen.height > cellLinkedList.Last.Value.Index * cellHeight + cellHeight))
{
}
else if ((cellLinkedList.First.Value.Index > 0) && (content.localPosition.y < cellLinkedList.First.Value.Index * cellHeight))
{
}

 

전체 코드

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

public abstract class GOTableViewController : MonoBehaviour
{
    //Cell을 생성하기 위한 cell 프리팹
    [SerializeField] protected GOTableViewCell cellPrefab; //protected는 상속받은 클래스에서 사용 가능. 어차피 Serialized이기 때문에 public은 있으나 마나.
    //Cell을 생성하게 될 content 객체
    [SerializeField] protected Transform content;
    //Cell 재사용을 위한 Queue 생성
    protected Queue<GOTableViewCell> reuseQueue = new Queue<GOTableViewCell>();
    //Cell 간의 연결고리를 관리하기 위한 List
    protected LinkedList<GOTableViewCell> cellLinkedList = new LinkedList<GOTableViewCell>();

    //cell의 높이
    private float cellHeight;
    //전체 셀의 개수
    private int totalRows;

    //자식 클래스에서 GOTableViewController가 표시해야 할 전체 Data의 수를 반환하는 메서드
    protected abstract int numberOfRows(); //추상 메서드 생성, 맨 위의 class도 추상 클래스로 변경(public abstract class GOTableViewController) 자식 클래스가 상속받아서 해당 부분을 채울 것이다. 틀만 만드는 느낌.
                                           //스크롤 바가 보이는 부분에다 맞추면 안되고 전체의 셀에 대한 스크롤 크기를 맞춰야 한다.
    protected abstract GOTableViewCell cellForRowAtIndex(int index); //추상 메서드 하나 더 생성.

    protected virtual void Start() //virtual 달아주는 이유는 부모 클래스에서 선언되었지만 자식 클래스에서 사용됨. abstract는 부모 클래스에서 내용을 정의할 수 없고 형태만 정의 가능. virtual은 부모에서도 구현 가능.
    {
        totalRows = this.numberOfRows(); //전체 몇 개의 값이 필요한지 얻어온다.

        float screenHeight = Screen.height; //화면의 높이 얻어오기. Canvas의 높이가 960으로 설정되어있다.

        cellHeight = cellPrefab.GetComponent<RectTransform>().sizeDelta.y; //셀의 높이. 200으로 설정되어있다.

        //표시해야 할 cell의 수 계산
        int maxRows = (int)screenHeight / (int)cellHeight + 2; //줄 수이기 때문에 int로 줄의 개수를 자연수로 받는다. 한 화면에 표시되는 셀의 개수는 960/200=4.8정도 된다. 아래 가려진 셀의 개수에 여유를 주기 위해 2를 더한다.
        maxRows = (maxRows > totalRows) ? totalRows : maxRows;

        //전체 셀을 포함하는 높이를 가질 수 도록 content의 크기를 조정
        RectTransform contentTransform = content.GetComponent<RectTransform>();
        contentTransform.sizeDelta = new Vector2(0, totalRows * cellHeight);

        //초기 Cell을 생성
        for(int i = 0; i < maxRows; i++)
        {
            GOTableViewCell cell = cellForRowAtIndex(i);//인덱스만 넣으면 셀이 만들어지도록.
            cell.gameObject.transform.localPosition = new Vector3(0, -i * cellHeight, 0); //첫 번째 셀 높이는 0, 두 번째 셀 높이는 -1 * 200 = -200
            //LinkedList에 Last Cell로 추가
            cellLinkedList.AddLast(cell);
        }
    }

    protected GOTableViewCell dequeueReuseableCell() //재사용이 가능한 Cell을 반환해주는 메서드. 재사용 cell 혹은 null이 반환된다.
    {
        if(reuseQueue.Count > 0) //쓸만한 셀이 있다면
        {
            GOTableViewCell cell = reuseQueue.Dequeue();
            cell.gameObject.SetActive(true);
            return cell;
        }
        else
        {
            return null; //재사용 가능한 셀이 있다면 Instantiate 할 수 있다고 생각할 수 있는데, 여기서 자식 형태의 cell을 만들어낼 수는 없다. 자식 쪽에서 만들어야 함. 넣었던 자식이 그대로 반환될 것.
        }
    }

    public void OnValueChanged(Vector2 vector)
    {
        Debug.Log($"Screen.height: {Screen.height}, Content.localPosition.y: {content.localPosition.y}");

        if ((cellLinkedList.Last.Value.Index < totalRows - 1) && (content.localPosition.y + Screen.height > cellLinkedList.Last.Value.Index * cellHeight + cellHeight)) // 부등호 왼쪽은 현재 위치 + 스크린 높이, 부등호 오른쪽은 마지막 보이는 셀의 개수 * 셀의 높이 + 여유 높이 하나 추가. 스크롤을 내려서 스크린 높이가 커지면,
        {
            //하단에 새로운 Cell이 만들어지는 상황
            //처음에 있던 Cell은 Reuse Queue에 저장
            LinkedListNode<GOTableViewCell> firstCellNode = cellLinkedList.First; //첫 번째 셀은 이미 화면을 벗어나서 없을 테니까.
            cellLinkedList.RemoveFirst(); //첫 번째 있던 값을 지워버리기
            firstCellNode.Value.gameObject.SetActive(false); //첫 번째 있던 셀을 비활성화하기
            reuseQueue.Enqueue(firstCellNode.Value); //reuseQueue에 저장하기

            //하단에 새로운 Cell 생성
            LinkedListNode<GOTableViewCell> lastCellNode = cellLinkedList.Last;
            int currentIndex = lastCellNode.Value.Index;
            GOTableViewCell cell = cellForRowAtIndex(currentIndex + 1);
            cell.gameObject.transform.localPosition = new Vector3(0, -(currentIndex + 1) * cellHeight, 0); //좌표를 마지막으로 보낸다.
            cellLinkedList.AddAfter(lastCellNode, cell);
            cell.gameObject.transform.SetAsLastSibling(); //게임 오브젝트 순서도 마지막으로 보낸다.
        }
        else if ((cellLinkedList.First.Value.Index > 0) && (content.localPosition.y < cellLinkedList.First.Value.Index * cellHeight))
        {
            //상단에 새로운 Cell이 만들어지는 상황
            //마지막에 있던 Cell은 Reuse Queue에 저장
            LinkedListNode<GOTableViewCell> lastCellNode = cellLinkedList.Last;
            cellLinkedList.RemoveLast();
            lastCellNode.Value.gameObject.SetActive(false);
            reuseQueue.Enqueue(lastCellNode.Value);

            //상단에 새로운 Cell 생성
            LinkedListNode<GOTableViewCell> firstCellNode = cellLinkedList.First;
            int currentIndex = firstCellNode.Value.Index;
            GOTableViewCell cell = cellForRowAtIndex(currentIndex - 1);
            cell.gameObject.transform.localPosition = new Vector3(0, -(currentIndex - 1) * cellHeight, 0);
            cellLinkedList.AddBefore(firstCellNode, cell);
            cell.gameObject.transform.SetAsFirstSibling();
        }
    }
}

양 끝으로 가도 오류가 나지 않고 잘 작동한다. 굿~

 

 


 

 

Unity Version Control 유니티 버전 컨트롤 사용하기

3D URP 프로젝트 생성, Use Unity Version Control을 체크하고 location은 Japan으로 설정한다.

 

 

에셋 스토어에서 무료 에셋을 하나 받는다.

 

Tanks! Tutorial | 자습서 | Unity Asset Store

Get the Tanks! Tutorial package from Unity Technologies and speed up your game development process. Find this & other 자습서 options on the Unity Asset Store.

assetstore.unity.com

 

 

아래의 Plastic SCM 홈페이지 아래로 내려가서 [Download Plastic SCM] 버튼을 눌러 다운로드 한다.

 

Plastic SCM - The Distributed Version Control for Big Projects

Looking for Plastic SCM? Plastic SCM was acquired by Unity in 2020 and is now a part of Unity DevOps, a modular solution from Unity Gaming Services. Unity DevOps is a tool specifically tailored for the rigors of game development, and gives users access to

www.plasticscm.com

 

 

[Window] - [Unity Version Control] 창을 켜고, 창에서 로그인, Create WorkSpace를 하면 다음과 같이 추가된 에셋들을 볼 수 있다.

 

 

Tanks 폴더 하나 만들고, Scenes 폴더를 제외한 모든 요소를 Tanks 폴더에 넣는다.

 

 

이미 체크가 되어있는 요소들을 [Check in Changes] 버튼으로 Check in 한다. github의 commit과 같다.

 

 

728x90
반응형