게임 로고 이미지

모든 게임에는 데이터가 있기 마련이고 이를 실제 사용하는 부분과 분리해 놓지않으면 확장성과 생산성이 떨어지는 코드를 만들어 냅니다. 이러한 문제를 해결할 수 있는 데이터매니저를 만들어보겠습니다. 이번시간에 만들 데이터 매니저는 json을 이용하여 저장하고 불러오는 기능을 토대로 구현합니다.


[데이터 매니저에 들어갈 기능]

  • json 형태로 데이터 저장
  • 데이터를 클래스 형태로 불러오기
    • json은 문자열이므로 클래스로 불러오는 기능을 만들 것 입니다.
  • 불러온 데이터를 손쉽게 사용하기
    • 메서드로 묶어서 손쉽게 사용하는 기능을 만들 것 입니다.

[데이터 매니저 구현]

베이스 클래스

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

public class DataManager<T>
{
    // 파일 경로
    private static string dataFilePath = Application.persistentDataPath + "/GameData.json";

    [Serializable]
    public class SerializableData
    {
        public int id;  // 식별자
        public T data;   // 실제 데이터

        public SerializableData(int id, T data)
        {
            this.id = id;
            this.data = data;
        }
    }

    public class DataSaver
    {
        // 데이터 리스트를 JSON 형식으로 변환하여 파일에서 관리
        public static void SaveData(List<SerializableData> dataList)
        {
            string jsonData = JsonUtility.ToJson(dataList);
            File.WriteAllText(dataFilePath, jsonData);
        }
    }

    public class DataLoader
    {
        // 데이터 파일에서 데이터 리스트를 불러옴
        public static List<SerializableData> LoadData()
        {
            List<SerializableData> dataList = new List<SerializableData>();

            if (File.Exists(dataFilePath))
            {
                string jsonData = File.ReadAllText(dataFilePath);
                dataList = JsonUtility.FromJson<List<SerializableData>>(jsonData);
            }
            else
            {
                Debug.LogWarning("불러올 데이터가 없습니다.");
            }

            return dataList;
        }

        // 데이터 리스트를 Dictionary로 변환하여 반환
        public static Dictionary<int, T> GetDataDictionary(List<SerializableData> dataList)
        {
            Dictionary<int, T> dataDictionary = new Dictionary<int, T>();

            foreach (var data in dataList)
            {
                dataDictionary[data.id] = data.data;
            }

            return dataDictionary;
        }

        // Dictionary에서 ID를 사용하여 데이터를 찾아 반환
        public static T GetDataById(Dictionary<int, T> dataDictionary, int id)
        {
            T resultData;

            if (dataDictionary.TryGetValue(id, out resultData))
            {
                return resultData;
            }
            else
            {
                Debug.LogWarning("ID에 해당하는 데이터를 찾을 수 없습니다.");
                return default(T);
            }
        }
    }
}

데이터 매니저의 기본이 되는 클래스입니다. 저장하고 불러오는 기능이 있으며, id를 통하여 필요한 데이터를 반환하는 기능도 있습니다. 성능에 영향을 미칠 것을 고려하여 Dictionary를 사용하였습니다. 불러올 정보가 적다면 상관 없지만 수천개라면 List로 Find하는 기능을 적절하지 못하기 때문에 이러한 기능을 추가했습니다.

또한 제네릭을 사용하여 어떤 클래스가 들어와도 사용하기 쉽도록 설계했습니다.

// DataManager을 실제 사용하는 클래스
public class DataManagerWrapperExample : MonoBehaviour
{
    [Serializable]
    public class PlayerData
    {
        public int playerScore;
        public string playerName;
    }

    private List<DataManager<PlayerData>.SerializableData> playerDataList;
    private Dictionary<int, PlayerData> playerDataDictionary;

    void Start()
    {
        // 캐싱
        playerDataList = DataManager<PlayerData>.DataLoader.LoadData();
        playerDataDictionary = DataManager<PlayerData>.DataLoader.GetDataDictionary(playerDataList);

        SavePlayerData();

        // ID로 불러오기 예제
        PlayerData loadedPlayerData = GetPlayerDataById(1);
        if (loadedPlayerData != null)
        {
            Debug.Log("Loaded Player Name: " + loadedPlayerData.playerName);
            Debug.Log("Loaded Player Score: " + loadedPlayerData.playerScore);
        }
    }

    void SavePlayerData()
    {
        playerDataList.Add(new DataManager<PlayerData>.SerializableData(1, new PlayerData { playerScore = 100, playerName = "Player1" }));
        playerDataList.Add(new DataManager<PlayerData>.SerializableData(2, new PlayerData { playerScore = 150, playerName = "Player2" }));
        DataManager<PlayerData>.DataSaver.SaveData(playerDataList);

        // 데이터 Dictionary 갱신
        playerDataDictionary = DataManager<PlayerData>.DataLoader.GetDataDictionary(playerDataList);
    }

    PlayerData GetPlayerDataById(int id)
    {
        return DataManager<PlayerData>.DataLoader.GetDataById(playerDataDictionary, id);
    }
}

베이스 클래스의 기능을 사용하여 실제 데이터를 캐싱하고 사용하는 클래스 입니다. PlayData클래스를 예시로 하였지만, 필요한 클래스를 선언하여 사용하시면 됩니다.

[장점]

어떠한 기능을 구현한 코드를 구현할 때 장,단점을 파악하는 것은 중요하기에 설명하겠습니다.

  • 제네릭 클래스를 이용한 중복 코드 최소화
    • 이를 사용하면 중복으로 사용되는 부분은 줄일 수 있으며 이것은 코드의 절약도 되지만 시간의 절약도 되어 생산성이 증가하는 효과를 냅니다.
  • 자료구조의 특성을 이용한 성능 최적화
  • 베이스 클래스와 실 사용 클래스 분리
    • 분리할 경우 코드 가독성을 올려주며, 어떤 프로젝트 던지 베이스 클래스를 가져와서 알맞는 Wrapper를 만들면 되므로 이식성을 올려줍니다.
  • 중앙 집중화 및 일관성 유지
    • 데이터를 관리하는 부분이 한곳으로 통일 돼 있어서 일관성이 유지되며, 여러 부분에서 동일한 데이터를 사용하기 때문에 중복코드를 최소화 할 수 있습니다.
  • 데이터 보안강화
    • 중요한 게임 데이터에 대한 접근을 통제할 수 있게 됩니다.
  • 테스트의 용이성
    • 단위 테스트와 통합 테스트를 수행하기 쉬운 환경이 제공됩니다. 쉽게 말해 테스트용 데이터를 따로 두어 테스트가 가능해 집니다.

[단점]

  • 로컬 사용을 기반으로 만들었습니다.
    • 이것은 상황에 따라 단점이 될 수도 장점이 될수도 있습니다. 만약 싱글 게임이라면 핵 사용률이 낮기에 서버비가 안들기에 장점이 될 수 있지만 멀티게임 같은 경우 핵 사용률이 높기에 서버저장이 기반이 되어야 합니다.

단점을 보완하기 위해서는 로컬 저장을 서버저장으로 변경 시켜주면 됩니다. 어떤 서버가 붙느냐에 따라서 구현이 달라지기 때문에 이 코드를 참고하셔서 구현하시면 될 것 같습니다. 불러오기와 저장하는 부분만 수정해 주면 되기 때문에 변경에 어려운 부분은 없습니다.

[마무리]

게임에서 데이터를 관리해주는 클래스는 무조건 있어야 합니다. 만약 지금까지 데이터와 실제 구현부를 구분하지 않고 구현하셨다면 확장성과 생산성 측면에서 많은 어려움을 겪었을 텐데 제공 해준 코드를 잘 이용 하셔서 이러한 문제를 해결하시기 바랍니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다