1. 데이터 연동을 위해 엑셀로 미션 테이블 제작
2. JSON 형식으로 변환
3. 뷰어에서 제대로 나오는지 확인
4. 메모장에 mission_data.json으로 저장
5. mission_data.json을 Resources 폴더 안에 넣기
6. 데이터 역 직렬화를 위해 MissionData 스크립트를 생성
7. MIssionData 스크립트 작성
8. MissionData를 로드할 DataManager 스크립트 생성
9. DataManager 스크립트 작성 (싱글톤)

***

Test05에 DataManager 스크립트가 이미 있기 때문에 namespace Test06으로 네임스페이스를 지정해준다.

***

10. DataManager를 싱글톤으로 instance를 통해 로드할 Test06UIMain 스크립트 생성 및 작성 (namespace를 Test06으로 지정)
11. UIMain (Canvas)에 Test06UIMain 스크립트를 부착
12. 씬을 실행하고 mission_data가 잘 로드 되는지 확인
13. 이전에 icon이미지의 크기가 제각각이라 각각 따로 크기를 잡아줘야 한다. 따라서 다음처럼 데이터 데이블 수정
14. mission_data.json 파일도 다음과 같이 수정
15. MIssionData 스크립트도 데이터 테이블에 따라 수정해준다
16. 만든 UIMissionCell을 프리팹으로 생성한 후, 하이어라키에 있는 UIMissionCell들은 모두 제거
17. 아이콘 이미지들을 모아서 관리할 아틀라스 생성(UIMissionAtals)
18. 사용할 아이콘 이미지 packing 하기
19. 미션 데이터의 저장을 테스트 해볼 버튼(btnSave) 생성
20. UIMissionCell, UIMissionScrollview 스크립트 생성

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

public class UIMissionCell : MonoBehaviour
{
    public enum eState
    {
        Doing, Complete
    }

    [SerializeField] private GameObject doingGo;
    [SerializeField] private GameObject completeGo;
    [SerializeField] private Image missionIcon;
    [SerializeField] private TMP_Text txtMissionName;
    [SerializeField] private TMP_Text txtMissionDesc;
    [SerializeField] private TMP_Text txtProgress;
    [SerializeField] private Slider slider;
    [SerializeField] private Image[] arrRewardIcons;
    [SerializeField] private TMP_Text[] arrRewardAmountTexts;
    [SerializeField] private Button btnClaim;

    private eState state = eState.Doing;
    private int doingAmount = 0;
    private MissionData data;

    public int Id
    {
        get
        {
            return this.data.id;
        }
    }

    private int count;
    public int Count
    {
        get
        {
            return this.count;
        }
    }

    public eState State
    {
        get
        {
            return this.state;
        }
        set
        {
            this.state = value;
            switch (this.state)
            {
                case eState.Doing:
                    this.doingGo.SetActive(true);
                    this.completeGo.SetActive(false);
                    break;
                case eState.Complete:
                    this.doingGo.SetActive(false);
                    this.completeGo.SetActive(true);
                    break;
            }
        }
    }


    public void Init(MissionData data, SpriteAtlas atlas)
    {
        this.data = data;
        this.state = eState.Doing;

        this.txtMissionName.text = data.name;
        this.txtProgress.text = string.Format("0 / {0}", data.goal);

        string desc = string.Format(data.desc, data.goal);
        Debug.Log(desc);
        this.txtMissionDesc.text = desc;

        Debug.LogFormat("<color=yellow>data.icon_name: {0}</color>", data.icon_name);
        Sprite sp = atlas.GetSprite(data.icon_name);
        Debug.LogFormat("sp: {0}", sp);
        this.missionIcon.sprite = sp;
        this.missionIcon.SetNativeSize();
        this.missionIcon.rectTransform.sizeDelta = new Vector2(data.mission_icon_width, data.mission_icon_height);

        foreach (var txtAmount in this.arrRewardAmountTexts)
            txtAmount.text = data.reward_amount.ToString();

        foreach (var rewardIcon in this.arrRewardIcons)
        {
            rewardIcon.sprite = atlas.GetSprite(data.reward_icon_name);
            rewardIcon.SetNativeSize();
            rewardIcon.rectTransform.sizeDelta = new Vector2(data.reward_icon_width, data.reward_icon_height);
        }

        float progress = (float)this.doingAmount / this.data.goal;
        Debug.LogFormat("{0}, {1}, <color=lime>{2}%</color>", this.data.id, this.data.name, progress * 100f);

        this.slider.value = progress;
    }

    public void ApplyCount(int count)
    {
        this.count = count;
        Debug.LogFormat("{0}({1})미션을 {2}개 진행", this.data.name, this.data.id, this.count);

        //0 ~ 1 
        float percent = (float)this.count / this.data.goal;
        this.slider.value = percent;
    }

    public int GetGoal()
    {
        if (this.data == null) return -1;  //아직 data가 없는상태 
        return this.data.goal;
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;

namespace Test06
{
    public class UIMissionScrollview : MonoBehaviour
    {
        [SerializeField] private Transform content;
        [SerializeField] private GameObject uiMissionCellPrefab;
        [SerializeField] private SpriteAtlas atlas;
        //객체를 그룹화 하고 관리 하는 2가지 방법 
        // 1. 배열, 2. 컬렉션 
        private List<UIMissionCell> uiMissionCells;

        public void Init()
        {

            //컬렉션 사용하기 전에 반드시 인스턴스화 
            this.uiMissionCells = new List<UIMissionCell>();

            Debug.LogFormat("icon_coins_pouch:  <color=lime>{0}</color>", this.atlas.GetSprite("icon_coins_pouch"));

            List<MissionData> missionDatas = DataManager.instance.GetMissionDataList();
            for (int i = 0; i < missionDatas.Count; i++)
            {
                MissionData data = missionDatas[i];
                this.CreateUIMissionCell(data);
            }
        }

        private void CreateUIMissionCell(MissionData data)
        {
            GameObject go = Instantiate(this.uiMissionCellPrefab, this.content);
            UIMissionCell cell = go.GetComponent<UIMissionCell>();
            cell.Init(data, atlas);

            //컬렉션에 추가 
            this.uiMissionCells.Add(cell);
        }

        public List<UIMissionCell> GetUIMissionCells()
        {
            return uiMissionCells;
        }
    }
}

21. UIMissionCell, UIMissionScrollview 스크립트 작성

 

22. UIMissionCell 프리팹을 오픈 후, UIMIssionCell 스크립트 부착 및 인스턴스 연결
23. UIMissionScrollview에도 UIMissionScrollview 스크립트 부착 및 인스턴스 연결
24. Test06UIMain 스크립트를 다음과 같이 수정
25. Scrollview인스턴스에는 UIMissionScrollview를 연결
26. 씬 실행시 로드를 통해 미션 셀이 제대로 생성 되는지 확인
27. 미션 진행도를 조작하기 위해 커스텀 인스펙터(UIMissionCellOnInspector)을 생성
28. UIMissionCellOnInspector 스크립트 작성
29. UIMissionCell 스크립트에도 UIMissionCell 프리팹에 커스텀 인스펙터를 추가하기 위해 다음과 같이 스크립트 수정
30. 씬을 실행 후, 미션 셀의 커스텀 인스펙터 조정 및 Apply Count를 클릭해서 조정한 수치만큼 로그가 출력되는지 확인
31. 커스텀 인스펙터로 slider를 조정하고 ApplyCount 버튼을 누르면 조정한 만큼 txtProgress와 slider가 변경 되도록 스크립트 수정
32. 씬을 실행 후, 정상적으로 작동이 되는지 확인
33. Save 버튼을 통해 저장될 테이블 양식 mission_info.xlsx
34. 미션 진행도(progress)가 저장될 파일 mission_info.json 생성 및 작성
35. 저장될 파일이므로  Application.persistentDataPath 위치에 넣어준다.
36. 직렬화를 위한 MissionInfo 스크립트 생성
37. MissionInfo 스크립트 작성

using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Test06;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

namespace Test06
{
    public class Test06UIMain : MonoBehaviour
    {
        [SerializeField] private UIMissionScrollview scrollview;
        [SerializeField] private Button btnSave;

        void Start()
        {
            DataManager.instance.LoadMissionData();
            scrollview.Init();

            this.btnSave.onClick.AddListener(() => {
                this.Save();
            });
        }

        private void Save()
        {
            Debug.Log("Save");
            Debug.Log(Application.persistentDataPath);

 

            List<UIMissionCell> cells = this.scrollview.GetUIMissionCells();

            
            List<MissionInfo> missionInfos = new List<MissionInfo>();//직렬화 대상 객체 만들기 
            foreach (UIMissionCell cell in cells)
            {
                Debug.LogFormat("Id: {0}, Count: {1}", cell.Id, cell.Count);
                MissionInfo info = new MissionInfo(cell.Id, cell.Count);
                missionInfos.Add(info);
            }
           
            string json = JsonConvert.SerializeObject(missionInfos); //직렬화 
            Debug.Log(json);


            string fileName = "mission_infos.json";  //파일로 저장 
            string path = string.Format("{0}/{1}", Application.persistentDataPath, fileName);
            Debug.Log(path);

            File.WriteAllText(path, json);

            Debug.Log("<color=yellow>SAVED</color>");

            EditorUtility.RevealInFinder(Application.persistentDataPath);
        }

    }
}

38. Test06UIMain 스크립트 수정

39. UIMissionCell 스크립트에 id와 count값을 가져올 수 있는 메서드 추가
UIMissionCell 스크립트의 ApplyCount 메서드 수정
40. UIMain의 btnSave 인스턴스에 btnSave 버튼 연결
41. UIMissionCell에서 Slider 수치 조정 후 Save 버튼 눌렀을때 저장이 되는지 확인

 

***

파일 로드

***

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


namespace Test06
{
    public class UIMissionCell : MonoBehaviour
    {
        public enum eState
        {
            Doing, Complete
        }

        [SerializeField] private GameObject doingGo;
        [SerializeField] private GameObject completeGo;
        [SerializeField] private Image missionIcon;
        [SerializeField] private TMP_Text txtMissionName;
        [SerializeField] private TMP_Text txtMissionDesc;
        [SerializeField] private TMP_Text txtProgress;
        [SerializeField] private Slider slider;
        [SerializeField] private Image[] arrRewardIcons;
        [SerializeField] private TMP_Text[] arrRewardAmountTexts;
        [SerializeField] private Button btnClaim;

        private eState state = eState.Doing;
        private int doingAmount = 0;
        private MissionData data;
        private MissionInfo info;

        public int Id
        {
            get
            {
                return this.data.id;
            }
        }

        private int count;
        public int Count
        {
            get
            {
                return this.count;
            }
        }

        public eState State
        {
            get
            {
                return this.state;
            }
            set
            {
                this.state = value;
                switch (this.state)
                {
                    case eState.Doing:
                        this.doingGo.SetActive(true);
                        this.completeGo.SetActive(false);
                        break;
                    case eState.Complete:
                        this.doingGo.SetActive(false);
                        this.completeGo.SetActive(true);
                        break;
                }
            }
        }


        public void Init(MissionInfo info, SpriteAtlas atlas)
        {
            this.info = info;
            this.data = DataManager.instance.GetMissionData(info.id);
            this.state = eState.Doing;

            this.txtMissionName.text = data.name;
            this.txtProgress.text = string.Format("{0} / {1}", info.count, data.goal);

            string desc = string.Format(data.desc, data.goal);
            Debug.Log(desc);
            this.txtMissionDesc.text = desc;

            Debug.LogFormat("<color=yellow>data.icon_name: {0}</color>", data.icon_name);
            Sprite sp = atlas.GetSprite(data.icon_name);
            Debug.LogFormat("sp: {0}", sp);
            this.missionIcon.sprite = sp;
            this.missionIcon.SetNativeSize();
            this.missionIcon.rectTransform.sizeDelta = new Vector2(data.mission_icon_width, data.mission_icon_height);

            foreach (var txtAmount in this.arrRewardAmountTexts)
                txtAmount.text = data.reward_amount.ToString();

            foreach (var rewardIcon in this.arrRewardIcons)
            {
                rewardIcon.sprite = atlas.GetSprite(data.reward_icon_name);
                rewardIcon.SetNativeSize();
                rewardIcon.rectTransform.sizeDelta = new Vector2(data.reward_icon_width, data.reward_icon_height);
            }

            float progress = (float)this.info.count / this.data.goal;
            Debug.LogFormat("{0}, {1}, <color=lime>{2}%</color>", this.data.id, this.data.name, progress * 100f);

            this.slider.value = progress;
        }


        public void Init(MissionData data, SpriteAtlas atlas)
        {
            this.data = data;
            this.state = eState.Doing;

            this.txtMissionName.text = data.name;
            this.txtProgress.text = string.Format("{0} / {1}", info.count, data.goal);

            string desc = string.Format(data.desc, data.goal);
            Debug.Log(desc);
            this.txtMissionDesc.text = desc;

            Debug.LogFormat("<color=yellow>data.icon_name: {0}</color>", data.icon_name);
            Sprite sp = atlas.GetSprite(data.icon_name);
            Debug.LogFormat("sp: {0}", sp);
            this.missionIcon.sprite = sp;
            this.missionIcon.SetNativeSize();
            this.missionIcon.rectTransform.sizeDelta = new Vector2(data.mission_icon_width, data.mission_icon_height);

            foreach (var txtAmount in this.arrRewardAmountTexts)
                txtAmount.text = data.reward_amount.ToString();

            foreach (var rewardIcon in this.arrRewardIcons)
            {
                rewardIcon.sprite = atlas.GetSprite(data.reward_icon_name);
                rewardIcon.SetNativeSize();
                rewardIcon.rectTransform.sizeDelta = new Vector2(data.reward_icon_width, data.reward_icon_height);
            }

            float progress = (float)this.info.count / this.data.goal;
            Debug.LogFormat("{0}, {1}, <color=lime>{2}%</color>", this.data.id, this.data.name, progress * 100f);

            this.slider.value = progress;
        }

        public int GetGoal()
        {
            if (this.data == null) return -1;    //아직 data가 없는상태 
            return this.data.goal;
        }

        public void ApplyCount(int cnt)
        {
            this.count = cnt;
            Debug.LogFormat("{0}({1})미션을 {2}개 진행", this.data.name, this.data.id, cnt);
            this.slider.value = cnt;
            this.txtProgress.text = string.Format("{0} / {1}", cnt, data.goal);

        }
    }
}

42. UIMissionCell 스크립트 수정 (Init)

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

namespace Test06
{
    public class UIMissionScrollview : MonoBehaviour
    {
        [SerializeField] private Transform content;
        [SerializeField] private GameObject uiMissionCellPrefab;
        [SerializeField] private SpriteAtlas atlas;
        //객체를 그룹화 하고 관리 하는 2가지 방법 
        // 1. 배열, 2. 컬렉션 
        private List<UIMissionCell> uiMissionCells;

        public void Init(List<MissionInfo> missionInfos)
        {

          
            this.uiMissionCells = new List<UIMissionCell>();  //컬렉션 사용하기 전에 반드시 인스턴스화 


            foreach (var missionInfo in missionInfos)
            {
                this.CreateUIMissionCell(missionInfo);
            }
        }

        private void CreateUIMissionCell(MissionInfo info)
        {
            GameObject go = Instantiate(this.uiMissionCellPrefab, this.content);
            UIMissionCell cell = go.GetComponent<UIMissionCell>();
            cell.Init(info, atlas);

            //컬렉션에 추가 
            this.uiMissionCells.Add(cell);
        }

        public List<UIMissionCell> GetUIMissionCells()
        {
            return uiMissionCells;
        }
    }
}

43. UIMissionScrollview 스크립트 수정 (Init, CreateMissionCell)

using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Test06;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

namespace Test06
{
    public class Test06UIMain : MonoBehaviour
    {
        [SerializeField] private UIMissionScrollview scrollview;
        [SerializeField] private Button btnSave;

        private string fileName = "mission_info.json";
        private string path;
        private List<MissionInfo> missionInfos;

        void Start()
        {
            this.path = string.Format("{0}/{1}", Application.persistentDataPath, fileName);

            DataManager.instance.LoadMissionData();

            
            this.btnSave.onClick.AddListener(() => //버튼 클릭 이벤트 부착 
            {
                this.Save();
            });


            if (this.IsNewbie())
            {
                
                List<MissionData> missionDatas = DataManager.instance.GetMissionDataList();//파일이 없다면 MissionData를 기반으로 MissionInfo 

               
                this.missionInfos = new List<MissionInfo>(); //컬렉션 인스턴스화 

                foreach (var data in missionDatas)
                {
                    MissionInfo info = new MissionInfo(data.id, 0);
                    this.missionInfos.Add(info);
                }
            }
            else
            {
                
                this.LoadMissionInfos();//파일이 있다면 불러와서 역직렬화
            }

            this.scrollview.Init(this.missionInfos);
        }

        private void LoadMissionInfos()
        {
            Debug.LogFormat("path: {0}", path);
            string json = File.ReadAllText(path);
          
            this.missionInfos = JsonConvert.DeserializeObject<MissionInfo[]>(json).ToList();  //역직렬화 
        }

        private bool IsNewbie()
        {

            Debug.Log(path);
            if (File.Exists(path)) //파일이 있다 = 기존유저 
            {
                
                return false;
            }
            else //파일이 없다 = 신규유저 
            {
                
                return true;
            }

        }

        private void Save()
        {
            Debug.Log("Save");
            Debug.Log(Application.persistentDataPath);

            
            List<UIMissionCell> cells = this.scrollview.GetUIMissionCells(); //MissionCell의 id와 count

           
            List<MissionInfo> missionInfos = new List<MissionInfo>(); //직렬화 대상 객체 생성 
            foreach (UIMissionCell cell in cells)
            {
                Debug.LogFormat("Id: {0}, Count: {1}", cell.Id, cell.Count);
                MissionInfo info = new MissionInfo(cell.Id, cell.Count);
                missionInfos.Add(info);
            }
            
            string json = JsonConvert.SerializeObject(missionInfos);//직렬화 

            File.WriteAllText(this.path, json);//파일로 저장 

            Debug.Log("<color=yellow>SAVED</color>");

            EditorUtility.RevealInFinder(Application.persistentDataPath);
        }

    }
}

44. Test06UIMain 스크립트 수정(파일이 없다면 MissionData를 통해 미션 셀을 생성, 있다면 기존 파일[mission_info]를 불러와서 역직렬화를 통해 미션 셀을 생성

 

45 씬 실행시, mission_info.json을 로드해서 미션 셀이 생성되는지 확인

 

+ Recent posts