DataManager

  • DataManager는 프로젝트에 사용되는 Resource 를 제외한 모든 데이터를 관리하는 Manger 그룹입니다.
  • 해당 프로젝트에서는 Enemy, Stage, Skill 등 다양한 데이터 정보를 csv파일에 작성
  • 해당 csv 파일을 Json 형태로 변환하여 다른 팀원이 해당 데이터를 사용 가능하도록 변형하는 역할도 겸합니다.
  • 문제는 Data의 타입의 무엇이 추가적으로 생길 지 프로젝트를 진행하면서 알 수 있기 때문에 일반적인 확장 가능한 Script를 작성해야 한다는 것입니다.
  • Interface와 <T> 을 사용하여 일반적으로 사용가능한 스크립트 구성을 해야 합니다.

아래는 초기 ProtoType의 DataManager입니다.

Manager

DataManager.cs

  • 데이터를 게임 시작시 Service 등록하고 다른 Script에서 해당 서비스에 접근 할 수 있도록 합니다.
public class DataManager : MonoBehaviour  
{  
	public Dictionary<string, EnemyData> Enemies = new Dictionary<string, EnemyData>();  
	private AddressableAsset _addressableAsset;  
	  
	private void Awake()  
	{  
		ServiceLocator.RegisterService(this);  
	}  
	  
	private void Start()  
	{  
		_addressableAsset = ServiceLocator.GetService<AddressableAsset>();  
	}  
	  
	public void InitializeData()  
	{  
		Enemies = LoadJson<EnemyDataLoader, string, EnemyData>("EnemyData").MakeData();  
	}  
	  
	private TLoader LoadJson<TLoader, TKey, TValue>(string path) where TLoader : ILoadData<TKey, TValue>  
	{  
		TextAsset textAsset = _addressableAsset.Load<TextAsset>(path);  
		Debug.Log(textAsset.text);  
		return JsonConvert.DeserializeObject<TLoader>(textAsset.text);  
	}  
}

Utility

  • Data의 Convert 또는 Load 를 위한 Utility 스크립트를 작성합니다.
  • 본 프로젝트는 Addressable Asset을 사용할 예정이며, 이는 ResourceManager를 이용하여 작성할 예정입니다.

    ServiceLocator.cs

  • ServiceLocator 패턴을 이용하기 위한 static class 작성
using System;  
using System.Collections.Generic;  
  
public static class ServiceLocator  
{  
	// 서비스들을 저장하는 Dictionary. Type을 키로 하고, 서비스 객체를 값으로 함.  
	private static readonly Dictionary<Type, object> Services = new Dictionary<Type, object>();  
	  
	/// <summary>  
	/// 서비스를 등록하는 메서드.  
	/// 제네릭 타입 T를 사용하여 어떤 타입의 서비스든 등록할 수 있음.  
	/// </summary>  
	/// <param name="service">등록할 서비스 객체</param>  
	public static void RegisterService<T>(T service)  
	{  
		// 서비스 객체를 Dictionary에 추가. 이미 존재하는 경우 해당 타입의 서비스를 덮어씀.  
		Services[typeof(T)] = service;  
	}  
	  
	/// <summary>  
	/// 등록된 서비스를 검색하는 메서드.  
	/// 제네릭 타입 T를 사용하여 원하는 타입의 서비스를 검색할 수 있음.  
	/// </summary>  
	/// <returns>검색된 서비스 객체. 해당 타입의 서비스가 없으면 예외 발생.</returns>  
	public static T GetService<T>()  
	{  
		// 요청된 타입의 서비스를 Dictionary에서 찾아 반환.  
		// 해당 타입의 서비스가 없는 경우 예외가 발생할 수 있음.  
		return (T)Services[typeof(T)];  
	}  
}

AddressableAsset.cs

  • Test용 Addressable class 작성
using System;  
using System.Collections.Generic;  
using UnityEngine;  
using UnityEngine.AddressableAssets;  
using UnityEngine.ResourceManagement.AsyncOperations;  
using Object = UnityEngine.Object;  
  
public class AddressableAsset : MonoBehaviour  
{  
	private Dictionary<string, Object> resources = new();  
	private DataManager _dataManager;  
	  
	private void Awake()  
	{  
		ServiceLocator.RegisterService(this);  
	}  
	  
	private void Start()  
	{  
		_dataManager = ServiceLocator.GetService<DataManager>();  
		LoadAllAsync<Object>("PreLoad", (s, i, arg3) =>  
		{  
			_dataManager.InitializeData();  
		});  
	}  
	  
	public void LoadAllAsync<T>(string label, Action<string, int, int> callback) where T : Object  
	{  
		var operation = Addressables.LoadResourceLocationsAsync(label, typeof(T));  
		operation.Completed += op =>  
		{  
		int loadCount = 0;  
		int totalCount = op.Result.Count;  
		  
			foreach (var result in op.Result)  
			{  
				LoadAsync<T>(result.PrimaryKey, obj =>  
				{  
					loadCount++;  
					callback?.Invoke(result.PrimaryKey, loadCount, totalCount);  
				});  
			}  
		};  
	}  
	  
	private void LoadAsync<T>(string key, Action<T> cb = null) where T : Object  
	{  
		if (resources.TryGetValue(key, out Object resource))  
		{  
			cb?.Invoke(resource as T);  
			return;  
		}  
	  
		string loadKey = key;  
		  
		AsyncOperationHandle<T> asyncResource = Addressables.LoadAssetAsync<T>(loadKey);  
		asyncResource.Completed += asyncOperationHandle =>  
		{  
			resources.Add(key, asyncOperationHandle.Result);  
			cb?.Invoke(asyncOperationHandle.Result);  
		};  
	}  
	  
	public T Load<T>(string key) where T : Object  
	{  
		if (!resources.TryGetValue(key, out Object resource)) return null;  
		return resource as T;  
	}  
}

마치며

처음 해보는 패턴의 작업이라 기존 참고 스크립트를 이해하는데 시간이 꽤 걸리고 있습니다. 다만, 이해를 하고 응용할 줄 안다면 빠르고 확장성이 보장된 서비스를 구축할 수 있을 것으로 예상되어 꼭 해결해야 할 것 입니다.