유니티3D CSV와 Json, ScriptableObject

JHO·2024년 9월 12일
0

유니티 3D

목록 보기
6/14
post-thumbnail

1.CSV


1-1. CSV란?

  • 데이터베이스를 이룰 수 있는 가장 기본적인 파일.
  • 각 행은 데이터의 레코드, 각 열은 데이터의 속성을 나타냄.
  • ',' 또는 '\'를 통해서 데이터를 구분.

1-2. 유니티에서의 사용법.

using System.IO;
public struct WeaponData
{
    public string index;
    public string name;
    public string attack;
    public string defense;
    public string description;
}
public class CsvParser : MonoBehaviour
{
    public List<WeaponData> weapons = new List<WeaponData>();
    private void Awake()
    {
        //persistentDathpath: 개인 로컬 저장소 경로 -> 게임 제작완료시
        string persPath = Application.persistentDataPath;
        // 프로젝트 경로 -> 게임 제작 중 사용
        string path = Application.dataPath + "/Datatables";
        if (Directory.Exists(path) == false)
        {
            Debug.Log("경로가 없습니다.");
            return;
        }
        string[] files = Directory.GetFiles(path, "*.csv");
        string file = File.ReadAllText(path + "/Datatable.csv");
        string[] lines = file.Split("\n");
        for (int i = 0; i < lines.Length; i++)
        {
            WeaponData weaponData = new WeaponData();
            string[] values = lines[i].Split(',', '\t');
            weaponData.index = values[0];
            weaponData.name = values[1];
            weaponData.attack = values[2];
            weaponData.defense = values[3];
            weaponData.description = values[4];
            weapons.Add(weaponData);
        }
    }
}
  • Application.persistentDatapath : 개인 로컬 저장소 경로
    -> 게임 제작완료 후 배포 시 사용.
    - Application.dataPath : 프로젝트 경로
    -> 게임 제작 중 사용.
  • Directory.Exists : 경로 확인.
  • Directory.GetFiles(path) : 두번째 인자가 없는 경우, 그 경로에 있는 모든 파일을 가져오고, 두번째 인자가 있는 경우, 해당하는 인자에 대한 파일만 가져옴.
  • File.ReadAllText : 특정 파일의 내용을 가져와 읽어옴.
  • file.Split: 특정 문자를 기준으로 문자열 나눔.

    - 꼭 CRLF로 해주기, LF같은 다른거로 하면 csv가 잘 안읽어와질수있음.

2. Json


2-1. 직렬화

  • 데이터를 전송/저장하기 위해 데이터를 선형적으로 나열하는 것.
  • 직렬화를 사용하면 게임 내 데이터 관리 및 게임 설정을 쉽게 관리할 수 있고, 직렬화를 네트워크상으로 보내는 데이터에 사용하기도 함.

2-2. Json

  • 데이터 직렬화 포맷을 사용.
  • key와 value 형식으로 데이터를 쌍으로 표현.

2-3. 유니티에서의 사용법.

using System.IO;
public class GameData
{
    public string name;
    public int level;
    public float rate;
}
public class JsonTester : MonoBehaviour
{
    [SerializeField] GameData gameData;
    [ContextMenu("Save")]
    private void Save()
    {
        string path = $"{Application.dataPath}/Save";
        if(Directory.Exists(path) == false)
        {
            Directory.CreateDirectory(path);
        }
        string json = JsonUtility.ToJson(gameData);
        File.WriteAllText($"{path}/save.txt", json);
    }
    [ContextMenu("Load")]
    private void Load()
    {
        string path = $"{Application.dataPath}/Save/save.txt";
        if(File.Exists(path) == false)
        {
            Debug.Log("불러올 세이브  파일 없음");
            return;
        }
        string json = File.ReadAllText(path);
        gameData = JsonUtility.FromJson<GameData>(json);
    }
}
  • [ContextMenu("name")] : name이름의 메뉴가 생성.
  • Directory.CreateDirectory : 원하는 경로의 파일 생성.
    -> 위 예시의 경우 Asset폴더안에 Save폴더 생성.
  • JsonUtility.ToJson: 직렬화.
  • File.WriteAllText($"path", json) : 해당 파일에 정보 입력.
    - File.ReadAllText(텍스트path): 해당 경로에서 읽어옴.
  • JsonUtility.FromJson : 역직렬화.

3. 스크립터블 오브젝트


3-1. ScriptableObject

  • 유니티에서 데이터를 미리 저장해두고 사용할 수 있도록 하는 클래스.
  • MonoBehaviour가 아닌 별도의 오브젝트로 관리 가능.
  • MonoBehaviour와 다르게 에셋으로 저장이 됨.

3-2. 사용법

[CreateAssetMenu(menuName = "Quest/NormalQuest", fileName  = "NormalQuest")]
public class Quest : ScriptableObject
{
    public string title;
    public string description;
    public int exp;
    public int gold;
    public GameObject[] reWard;
}
  • [CreateAssetMenu(menuName = "Quest/NormalQuest", fileName = "NormalQuest")]
    -> menuName은 오른쪽 마우스클릭시 나오는 Menu버튼 생성 후, Menu의 이름 설정.
    -> 위의 예시의 경우 Quest - NormalQuest.
    -> fileName은 Quest-NormalQuest를 통해 생성 후, 기본적으로 사용되는 이름.
  • 주의할점은, 데이터 컨테이너 용도로 사용 시, 공통된 데이터를 저장하기 때문에,
    개별적으로 다른 데이터를 가져야하는 변수가 있는 경우 사용x!
    -> ex) Monster의 Hp의 경우 Monster마다 Hp값이 다르기에 이런 경우는 사용 x.
  • 또한, 하나의 값을 변경하는 경우, 스크립터블 오브젝트를 가진 모든 오브젝트의 값도 다 변경됨.
    [CreateAssetMenu(menuName = "Skill")]
    public class Skill : ScriptableObject
    {
       void Use()
       {
           Debug.Log("hi");
       }
  • 함수를 따로 scriptableobject를 통해 파일 형식으로 관리도 가능.

3-3. 장점

  • MonoBehaviour는 기본적으로 게임 오브젝트에 부착되어야 하며, 각 게임 오브젝트마다 독립적으로 인스턴스화되고, 각각의 MonoBehaviour가 메모리 내에 존재.
    -> 이로 인해, 같은 데이터를 여러 번 복제해야 할 수 있습니다.
    -> 반면, ScriptableObject는 게임 오브젝트와 독립적으로 존재할 수 있으며, 한 번 인스턴스화된 스크립터블 오브젝트를 여러 곳에서 참조 가능. 같은 데이터가 중복되지 않기 때문에 메모리 절약에 유리.
  • 런타임에 변경할 필요가 없고, 공용Data의 경우 SO를 활용하면 좋다.
    -> 변하지 않는 Data의 경우, Scriptableobject를 참조하여 Instantiate을 통해 복제한 데이터를 사용할 수 있으나, 이러면 기존의 SO 장점을 상쇄.

3-4. 활용방안 - 커맨드 패턴

  • Scriptable Object를 상속받아 커맨드 패턴을 활용하여 알맞은 함수를 사용하는 것도 가능.
  • 또는 객체지향원칙을 조금 무시하더라도 커맨드패턴이 아닌
    단순히 public virtual void Use()를 통해, ItemData를 상속받아 자식클래스에서 재정의 하여 사용하는 법도 있음.

3-5. 활용방안 - 옵저버 패턴

  • 이벤트를 활용한 옵저버 방식도 가능하다.
    -> ScriptableObject 자체가 유일무이하기 때문에 싱글톤 패턴 대신에 ScriptableObject를 사용하여 구현이 가능하다.
    -> 예로 싱글톤패턴을 적용한 클래스에 이벤트를 구현하고, 다른 Observer에 이벤트를 연결하는 것보단, 싱글톤패턴을 사용하지 않고 ScriptalbeObject를 사용하는것이 통합테스트면에서도 이점을 가지고 올 수 있다.

3-6. 주의점

  • 에디터에서는 스크립터블 오브젝트를 언제나 생성해서 데이터 저장이 가능하지만, 배포된 빌드에서는 스크립터블 오브젝트를 새로 생성할수 없어 기존의 스크립터블 오브젝트의 데이터만을 사용
  • 에셋파일형식으로 저장되기때문에, 에셋번들로 빌드하고 배포하는 방식으로 게임 업데이트 가능.
  • 게임 빌드에 포함되기에 사용자가 설치된 게임data를 수정가능. 싱글겜에서는 크게 영향이 없을수있으나 멀티겜에서는 영향이 크므로 중요한 data를 스크립터블오브젝트로 저장하면 안됨
  • SO를 참조하고 내부에서 새로운 SO를 Instantiate를 하는 방식은 기존의 SO의 장점을 상쇄하기 때문에, 가능하면 불변하는 Data와 관련해서 SO로 관리하는것이 좋음
profile
개발노트

0개의 댓글