유니티 UI 동적 생성 - yuniti UI dongjeog saengseong

UGUI를 이용, json 파일을 통해서 scroll view의 항목들을 동적으로 생성해보았다.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

using UnityEngine.U2D;

public class MissionListItem : MonoBehaviour

{

public int id;

public Image rewardIcon, Thumb;

public Image[] Stars;

public Text rewardText, MissionName;

public GameObject bg;

public Button claim;

public void Init(int id, string rewardIcon, string Thumb, int rewardText, string MissionName, int goalValue, string hex_color)

{

this.Claim();

this.gameObject.transform.SetParent(GameObject.Find("Contents").transform);

var atlas = GameObject.FindObjectOfType<TestUGUI>().atlas;

this.bg.SetActive(false);

this.id = id;

this.rewardIcon.sprite = atlas.GetSprite(rewardIcon);

this.Thumb.sprite = atlas.GetSprite(Thumb);

this.rewardText.text = rewardText.ToString();

Color color;

ColorUtility.TryParseHtmlString(hex_color, out color);

this.rewardText.color = color;

this.MissionName.text = string.Format(MissionName,goalValue.ToString());

}

private void Claim()

{

this.claim.onClick.AddListener(() =>

{

var dm = DataManager.GetInstance();

Debug.LogFormat("id : {0}, reward : {1}, reward_Value : {2}"this.id, this.rewardIcon.name, this.rewardText.text);

});

}

}

cs

GameObject 동적으로 생성 하기

GameObject 를 미리 만들어 두지 않고 게임 실행도중에 동적으로 생성해야 될때가 있다.

주로 몬스터나 총알을 구현할때 사용한다.

코드

public GameObject thePrefab; // 생성할 프리팹, 인스펙터에서 따로 지정해줘야함

private Vector3 m_vPos;

private Vector3 m_vAngle;

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

  // 생성할 프리팹, 위치, 각도

GameObject Instance = (GameObject) Instantiate(thePrefab, m_vPos, m_vAngle ); 

}

출처

http://blog.naver.com/PostView.nhn?blogId=37441&logNo=80124412846&parentCategoryNo=16&viewDate=&currentPage=1&listtype=0

http://www.devkorea.co.kr/reference/Documentation/ScriptReference/Object.Instantiate.html

 게임을 만들다 보면 무한한 아이템을 담은 인벤토리등의 스크롤을 구현해야 할 일이 많다.

 무한이 아니더라도 모바일 환경에서 200개가 넘는 동적 오브젝트를 실시간으로 생성하며 관리하는 것은 리소스의 낭비로 이어지고 부하를 초래하여 플레이 경험과 질적 하락을 일으키게 된다.

 이를 위해 이런 다수의 아이템 목록을 UI등에 구현할때 마치 오브젝트 풀과 같이 한정된 갯수의 오브젝트를 생성한후 이를 돌려쓰는 방식을 많이 사용한다.

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

public class makeList : MonoBehaviour
{
    [SerializeField] RectTransform _rtParent = null; //생성될 오브젝트가 배치될 부모 Rect
    [SerializeField] GameObject _gbjSlot = null; //동적 생성할 프리팹
    [SerializeField] ScrollRect _scroll = null; //스크롤뷰 갱신 확인용


    public int _slotCount = 9; //생성할 논리적 오브젝트 수
    public int _makeCount = 10; //물리적으로 생성하여 사용할 오브젝트 풀 갯수
    
    int _index = 0; //scrollView에서 최상단 오브젝트의 논리적 번호
    RectTransform _slotRect = null; //전역으로 사용할 rect사이즈
    GameObject[] _slots = null; //오브젝트 풀


    private void Awake()
    {
        _scroll.onValueChanged.AddListener(OnScrollChange);
    }

    private void Start()
    {
        _slotRect = _gbjSlot.GetComponent<RectTransform>();

        _rtParent.sizeDelta = new Vector2(_rtParent.sizeDelta.x, _slotRect.sizeDelta.y * _slotCount);
        _slots = new GameObject[_slotCount];

        for(var i = 0; i < _makeCount; ++i)
        {
            GameObject gbj = Instantiate(_gbjSlot, _rtParent);
            gbj.name = string.Format("item {0}",i);
            gbj.transform.localPosition = new Vector2( gbj.transform.localPosition.x,-_slotRect.sizeDelta.y * i);
            Refresh(gbj,i);
            _slots[i] = gbj;
        }
    }

    void OnScrollChange(Vector2 vec)
    {
        //index 가져오기
        //위 아래 옮겨주기
        int index = getCurrentIndex();
        GameObject objIndex = _slots[index];
        if(objIndex == null)
        {
            int next = index + _makeCount;
            if (next > _slotCount - 1)
                return;
            else
            {
                GameObject objNow = _slots[next];
                if(objNow != null)
                {
                    _slots[next] = objIndex;
                    _slots[index] = objNow;
                    Refresh(_slots[index], index);
                }
            }
        }
        else
        {
            if(index > 0)
            {
                GameObject obj = _slots[index - 1];
                if (obj == null)
                    return;
                int next = index - 1 + _makeCount;
                if (next > _slotCount - 1)
                    return;
                else
                {
                    GameObject objNow = _slots[next];
                    if(objNow == null)
                    {
                        _slots[next] = obj;
                        _slots[index - 1] = objNow;
                        Refresh(_slots[next], next);
                    }
                }
            }
        }
    }

    void Refresh(GameObject obj, int indexRefresh)
    {
        obj.transform.name = string.Format("item {0}",indexRefresh);
        Vector3 vec = getLocationAppear(obj.transform.localPosition, indexRefresh);
        obj.transform.localPosition = vec;
    }

    Vector3 getLocationAppear(Vector2 initVec, int locaiton)
    {
        Vector3 vec = initVec;
        vec = new Vector3(vec.x,-( _slotRect.sizeDelta.y * locaiton), 0);
        return vec;
    }

    int getCurrentIndex()
    {
        int index = (int)(_rtParent.anchoredPosition.y/ _slotRect.sizeDelta.y);
        if (index < 0)
            index = 0;
        if (index > _slotCount - 1)
            index = _slotCount - 1;
        return index;
    }


    //빠른 이동으로 인한 버그 픽스가 필요함
}

(*필드 변수는 주석을 참고하자)

 전체 코드이며 코드는 크게 1. 오브젝트 풀 초기화, 2. 인덱스/위치 연산, 3. 갱신 세부분으로 나뉜다.

 1. 오브젝트 풀 초기화 Start()

 : MonoBehaviour 라이프 사이클의 이벤트 함수인 Start()에서 오브젝트 풀을 원하는 갯수만큼 동적으로 생성한 오브젝트로 초기화 해준다. 현재의 코드에선 Vertical 형 스크롤뷰만 지원하고 있고 위치를 생성한 이후 현재는 의미없지만 Refresh함수를 한번 더 거쳐 위치를 다시 초기화 하고 있다.

 2. 인덱스/위치 연산 getCurrentIndex(), GetLocationAppear()

 : 오브젝트가 배치되는 오브젝트의 SizeDelta와 부모 Rect의 anchoredPosition을 통해 현재최상단 아이템의 논리적 인덱스를 가져와서 이를 기반해 생성/이동할 오브젝트의 위치를 연산한다.

 3. 갱신 onScrollChange()

 : 위의 인덱스 확인을 통해 해당 인덱스의 오브젝트를 오브젝트 풀에서 확인하고 없을 경우 마지막 오브젝트로 교체하고 위치를 초기화 한다.

 인덱스보다 작은 오브젝트가 존재하고 다음 생성할 오브젝트의 인덱스가 논리상 오브젝트 갯수보다 커지면 이미 지나간 최상단 이전의 오브젝트로 갱신하고 위치를 초기화 한다.

 해당 코드는 업데이트를 기반하여 실행되기 때문에 한번의 업데이트때 두개이상의 오브젝트가 체크되지 못하여 이빨이 빠지는등의 버그가 있을 수 있다. 또 생성 부분에 추후 활용하기 위한 불필요한 코드도 존재한다. 무엇보다 그리드형 UI를 표시하기 위하여 추가 작업이 필요하다.