공부/인프런 - Rookiss

Part 3-12-1. Data : Json을 이용한 Data Manager

셩잇님 2023. 8. 29. 14:31
반응형

 

 

Data

 

Data Manager

게임 내의 모든 수치, 데이터들을 관리하는 Manager

 

 

하드 코딩으로 수치를 세팅하고 바꾸는 것은 관리차원에서도 문제가 되고 더 큰 문제는 하드코딩된 수치는 exe 실행파일에 묶여 들어가기 때문이다. 나중에 수치를 변경해야할 업데이트 사항이 생긴다면 수정해서 만든 새로운 exe를 배포를 아예 다시 해야하는 일이 발생하는 것이다.

 

👉 따라서 모든 수치와 연관된 모든 데이터들을 모아둔 것을 따로 별도의 파일로 만든다. 일반적으로 json이나 XML파일로 만든다. 게임을 실행했을 때 웹 통신을 이용하여 데이터 파일 내용을 살짝 수정하기만 하면 되기 때문에 게임을 실행할 때 그 바뀐 데이터 파일을 로드해서 실행하게 되므로 게임 실행 파일을 수정하고 새배포 해야하는 그런 일은 하지 않아도 된다!

 

👉 서버와 클라이언트에서 바라보는 데이터가 일치해야한다. 서버에서는 얘가 레벨 10으로 알고 있는데 클라이언트에서는 레벨 30이고 이러면 안된다. 그러니 서버와 클라이언트에서도 동일한 데이터 파일에 접근하게 해야한다. 그래서 수치 데이터 파일을 따로 관리해야 하는 것이다.

 


 

데이터 파일
파일 포맷

 

 

파일 포맷을 정해서 사용해야 하는데 주로 json 아니면 XML 파일을 쓴다. 요샌 json 많이 사용한다. json이 좀 더 가독성이 좋고 깔끔하며 빠르다.(Json 형식을 생각해보라. 딱 봐도 파싱 할게 별로 없음) 그래도 XML도 때에 따라 좋긴하다. 데이터가 복잡하고 계층적인 구조를 가진다면 오히려 XML이 더 좋을 수 있다. 보통 실무에서는 XML로 데이터 파일을 작성하고 나중에 서버에 올릴 때는 Json으로 변환한다고 한다.

현업에서는

1. 변하지 않는 고정 데이터 👉 json, xml 같은 데이터 시트로 관리
ex) 무기의 정보, 강화 확률(강화 확률 같은건 클라이언트가 알면 안되므로 서버에서 이렇게 클라가 알면 안되는 데이터들을 또 다른 파일로 관리하기도 한다.) 👉 그래서 이런 데이터는 서버에서 로드하는 듯 하다.

 

2. 유저마다 다른 데이터 👉 MySQL 같은 데이터 베이스로 관리
ex) 유저의 인벤토리 정보 (DB의 아이템 테이블에서 따로 관리)


Json 데이터 파일 작성
텍스트 파일을 만들고 그 확장자를 json으로 변경하면 손쉽게 json파일이 된다. 그리고 이를 VS에서 열어서 작성할 수도 있다.

 

Json 문법

 

2 가지만 알면 된다.

[] 👉 배열, 리스트

{} 👉 구조체

{
  "stats": [
    {
      "level": "1",
      "hp": "100",
      "attack": "10"
    },
    {
      "level": "2",
      "hp": "150",
      "attack": "15"
    },
    {
      "level": "3",
      "hp": "200",
      "attack": "20"
    }
  ]
}

 

stats라는 리스트에 3 가지의 구조체가 있는 것이 된다. 구조체나 리스트의 마지막 원소에는 ',' 쉼표 넣지 않는다. 즉. 위 형태에서는 구조체 하나 하나가 Stat 객체가 된다. 👉Stat 객체 밑의 Stat 클래스 참고

 

 


 

 

코드

 

📜DataManager

게임이 시작되면 이 JSON 데이터 파일을 로드하는 함수 제공.

 

📜Data.Contents

로드한 json파일에서 데이터들을 읽어와 게임 메모리에 저장해야 하는데 저장을 📜Data.Contents안의 여러가지 데이터 클래스에서 한다. 예를들면 데이터 형태 혹은 데이터 클래스 👉 스탯, 무언가의 위치, 확률 등등…

 

📜DataManager

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

public interface ILoader<Key, Value>
{
    Dictionary<Key, Value> MakeDict();
}

public class DataManager
{
    public Dictionary<int, Stat> StatDict { get; private set; } = new Dictionary<int, Stat>();

    public void Init()
    {
        StatDict = LoadJson<StatData, int, Stat>("StatData").MakeDict();
    }

    Loader LoadJson<Loader, Key, Value>(string path) where Loader : ILoader<Key, Value>
    {
		TextAsset textAsset = Managers.Resource.Load<TextAsset>($"Data/{path}"); // text 파일이 textAsset에 담긴다. TextAsset 타입은 텍스트파일 에셋이라고 생각하면 됨!
        return JsonUtility.FromJson<Loader>(textAsset.text);
	}
}

 

ILoader<Key, Value> 인터페이스 👉 Dictionary를 리턴하는 MakeDict 함수를 구현하도록 강제한다.

모든 종류의 데이터 클래스들(스탯 클래스, 확률 클래스 등등)들은 이 함수를 만들어야 한다. 즉 관련있는 모든 데이터 객체들은 Dictionary 에 모여야 한다. 즉 모든 Stat 데이터 객체들은 같은 Dictionary에 한데 모여야 한다.

 

📜DataManager

StatDict

‘스탯’ 관련 데이터들을 담는 Dictionary 프로퍼티.

Key는 int 이며(예를 들어 몬스터 스탯은 3번, NPC 스탯은 4 번 이런식으로 id를 부여해서 데이터를 찾는 경우가 실무에선 많다.)

Value는 Stat 객체이다. (밑에서 후술할 📜Data.Contents 에 있는 스탯데이터 클래스)

 

Init

밑의 LoadJson 함수를 사용하여 게임이 시작되면 “SatData.json” 텍스트 파일로부터 읽어들인 데이터가 저장된 Stat 객체들이 담긴 Dictionary 를 만들어 StatDict 프로퍼티에 리턴한다.

 

LoadJson

제네릭 매개변수

Loader : ILoader 를 상속받는 객체. 예를 들어 Stat 객체들을 Dictionary에 담아 리턴하는 기능을 하는 StatData 객체가 들어갈 것.

Key : 대체로 몬스터 스탯은 3번, NPC 스탯은 4 번 이런식으로 int ID 형태로 찾겠지만 string으로 데이터를 찾으려 할 수도 있기 때문에

Value : Stat 객체가 들어올 것이다. 스탯 뿐만 아니라 다양한 형식의 데이터들이 있을 것이므로 역시 제네릭하게..

 

📜Data.Contents

로드한 데이터들을 들고 있을 파일

 

#region Stat

[Serializable]
public class Stat
{
	public int level; // ID
	public int hp;
	public int attack;
}

[Serializable]
public class StatData : ILoader<int, Stat>
{
	public List<Stat> stats = new List<Stat>();  // json 파일에서 여기로 담김

	public Dictionary<int, Stat> MakeDict() // 오버라이딩
	{
		Dictionary<int, Stat> dict = new Dictionary<int, Stat>();
		foreach (Stat stat in stats) // 리스트에서 Dictionary로 옮기는 작업
			dict.Add(stat.level, stat); // level을 ID(Key)로 
		return dict;
	}
}

#endregion
{
  "stats": [
    {
      "level": "1",
      "hp": "100",
      "attack": "10"
    },
    {
      "level": "2",
      "hp": "150",
      "attack": "15"
    },
    {
      "level": "3",
      "hp": "200",
      "attack": "20"
    }
  ]
}

 

Json으로부터 데이터를 얻어오려면 이름과 타입이 전부 일치해야 한다.


Stat 클래스! Stat 형태의 데이터

하나 하나의 Stat 데이터 객체 👉 Stat

level을 스탯들을 구분하는 ID로 쓸 것. 몬스터 스탯은 level 값이 3..

 

📜DataManager의 LoadJson<StatData, int, Stat>(“StatData”)을 실행하면 Loader에 StatData가 대입되므로 LoadJson은 StatData 객체를 리턴한다. StatData의 유일한 멤버인 stats 리스트에, 이름이 일치하는 Json의 stats의 3가지 구조체 정보를 각각 Stat 객체로서 대입한다. 해당 구조체에서도 level, hp, attack은 Stat의 멤버들에 대응한다.

 

json 파일의 stats 리스트 이름과 일치하는 stats List에 3가지 Stat 객체가 로드되고 각각의 멤버에 하나하나 이름이 일치하는 json 구조체가 들어간다. 👉 stats 리스트 (3가지 Stat 객체가 들어 있는) level, hp, attack 이렇게 Json 파일의 이름과 대입하려는 멤버들이 전부 일치해야 한다.

 

실행이 다 끝나고나면 stats 리스트에 각각의 3가지 멤버들이 Json 파일로부터 대입받아 초기화된 3가지 Stat 객체가 들어가게 된다.  그리고 SaveData 인스턴스를 생성하여 리턴하게 된다.

 

json 파일로부터 stats 리스트에 입력 받았지만 이 리스트의 Stat객체들을 Dictionary에 옮긴다. 왜냐면 ID로 접근하듯이 찾을 것이기 때문에 인덱스보단 Key로 해당 스탯 데이터를 찾는게 더 편하기 때문이다. 이를 MakeDict 메서드에서 진행한다.

 

📜Manager

DataManager _data = new DataManager();
public static DataManager Data { get { return Instance._data; } }

    static void Init()
    {
        if (s_instance == null)
        {
			//... 
            s_instance._data.Init();
        }		
	}

 

데이터는 게임이 시작되자마자 로드되어야 하며 거의 게임 내내 들고 있어야 하기 때문에 Clear() 에서 Data.Clear() 하는 것은 해주지 않음. 구현도 안하고!

 

📜GameScene

public class GameScene : BaseScene
{
    protected override void Init()
    {
        //...
        Dictionary<int, Stat> dict_stat = Managers.Data.StatDict;
    }

 

 

 

반응형