유저가 가진 여러 종류의 데이터 형식에 따른 데이터들을 관리하기 위해 UserDataManager을 구현할 것이다.
1. 개요
2. 기능 명세
3. 기능 구현
4. 테스트
5. 후기
1. 개요
현재 유저의 데이터 종류는 UserGoodsData, UserSetting이다. 해당 데이터들에 대한 데이터 초기화, 데이터 저장, 데이터 로드 함수 등을 만들어 데이터들을 전체적으로 관리 가능하게 하는 UserDataManager을 만드는 것이 목표이다.
2. 기능 명세
필요한 기능은 다음과 같다.
* 처음에 유저 데이터 초기화(저장된 데이터가 없을 경우)
* 모든 데이터 로드하는 LoadAllData()
* 모든 데이터 저장하는 SaveAllData()
* 데이터를 초기화하는 ResetAllData()
* 특정 데이터만 저장하는 SaveSpecificData()
* 특정 데이터만 삭제하는 DeleteSpecificData()
* 앱이 종료되거나 포커스를 잃었을 때 저장하는 OnApplicationQuit()
* 모바일의 경우, 홈 화면으로 나갔을 때 자동저장되는 OnApplicationPause()
3. 기능 구현
전체적인 코드는 다음과 같다.
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using BalancingLibra.Data;
public class UserDataManager : SingletonBehaviour<UserDataManager>
{
private UserGoodsData _goodsData;
private UserSettingData _settingData;
private List<IUserData> _userDataList;
public UserGoodsData Goods => _goodsData;
public UserSettingData Setting => _settingData;
protected override void Init()
{
base.Init();
_goodsData = new UserGoodsData();
_settingData = new UserSettingData();
_userDataList = new List<IUserData>
{
_settingData,
_goodsData
};
LoadAllData();
}
public void LoadAllData()
{
Logger.Log("UserDataManage : Load All Data", this);
foreach(var data in _userDataList)
{
data.LoadData();
}
}
public void SaveAllData()
{
Logger.Log("UserDataManager : Save All Data", this);
foreach(var data in _userDataList)
{
data.SaveData();
}
}
public void ResetAllData()
{
Logger.Log("UserDataManager : Reset All Data", this);
foreach(var data in _userDataList)
{
data.SetDefaultData();
}
SaveAllData();
}
public void SaveSpecificData(IUserData targetData)
{
if(targetData == null)
{
Logger.Log("SaveSpecificData : Target is null");
return;
}
bool success = targetData.SaveData();
if(success)
Logger.Log($"SaveSpecificData : Success ({targetData.GetType().Name})");
else
Logger.Log($"SaveSpecificData : Failed ({targetData.GetType().Name})");
}
public void DeleteSpecificData(IUserData targetData)
{
if(targetData == null)
{
Logger.Log("DeleteSpecificData : Target is null");
return;
}
targetData.DeleteData();
Logger.Log($"DeleteSpecificData : Deleted ({targetData.GetType().Name})");
}
private void OnApplicationQuit()
{
SaveAllData();
}
private void OnApplicationPause(bool pauseStatus)
{
if(pauseStatus)
{
SaveAllData();
}
}
}
데이터 종류가 여러 개이기 때문에, 일관성 있는 처리르 위해 해당 데이터들을 리스트로 받았다.
그리고 외부에서 해당 데이터에 접근하는 경우를 위해 직관적이고 안전하게 접근하기 위해 프로퍼티로도 처리했다.
유저가 게임을 종료하거나 백그라운드로 보내는 임의의 상황을 대비해 자동 저장 기능을 추가했다. 이 경우는 유니티 라이프사이클 함수여서 유니티 내부 시스템이 리플렉션으로 직접 기능을 찾아 실행하므로 private/public 여부가 중요하지 않다. 하지만 실수로 해당 함수를 실행하면 안되므로 private 처리를 했다.
여기서 데이터 삭제 기능을 구현하기 위해 IUserData형식에 Delete()를 추가해 해당 클래스를 상속받은 다른 유저데이터들에도 적용했다.
위 기능 명세에 작성한 기능들을 구현한다.
4. 테스트
테스트 코드는 위와 같이 작성한 뒤, 테스트할 씬에 게임 오브젝트를 만들어 테스트했다.
using UnityEngine;
using BalancingLibra.Data; // 네임스페이스 필수
public class DataTestManager : MonoBehaviour
{
private void Update()
{
// 1. 데이터 변경 (돈 벌기)
if (Input.GetKeyDown(KeyCode.Space))
{
UserDataManager.Instance.Goods.Gold += 1000;
Debug.Log($"[Test] Gold 증가: {UserDataManager.Instance.Goods.Gold}");
}
// 2. 특정 데이터만 저장 (Goods만 저장)
if (Input.GetKeyDown(KeyCode.S))
{
UserDataManager.Instance.SaveSpecificData(UserDataManager.Instance.Goods);
Debug.Log("[Test] Goods 데이터 개별 저장 요청");
}
// 3. 특정 데이터만 삭제 (Setting만 삭제)
if (Input.GetKeyDown(KeyCode.D))
{
UserDataManager.Instance.DeleteSpecificData(UserDataManager.Instance.Setting);
Debug.Log("[Test] Setting 데이터 개별 삭제 요청 (Sound 초기화됨)");
}
// 4. 전체 초기화
if (Input.GetKeyDown(KeyCode.R))
{
UserDataManager.Instance.ResetAllData();
Debug.Log("[Test] 모든 데이터 초기화 완료");
}
// 5. 데이터 조회 (현재 상태 확인)
if (Input.GetKeyDown(KeyCode.P))
{
long gold = UserDataManager.Instance.Goods.Gold;
bool sound = UserDataManager.Instance.Setting.Sound;
Debug.Log($"[Test] 현재 상태 -> Gold: {gold} | Sound: {sound}");
}
// 6. PlayerPrefs 생성 (Sound 값 변경 및 저장)
if (Input.GetKeyDown(KeyCode.A))
{
UserDataManager.Instance.Setting.Sound = !UserDataManager.Instance.Setting.Sound;
UserDataManager.Instance.SaveSpecificData(UserDataManager.Instance.Setting);
Debug.Log($"[Test] Sound 변경 및 저장됨: {UserDataManager.Instance.Setting.Sound}");
}
}
}
테스트 방법은 다음과 같다.
1) SaveSpecificData로 저장 기능 적용 여부 확인
1.1) 이전에 만든 에디터를 사용해 Tools > Clear Save Data로 UserGoodsData.json 초기화
1.2) Space키를 눌러서 Gold 수치가 증가 로그 확인 (Gold: 3000)
1.3) S키를 누르고 로그 확인 ( SaveSpecificData : Success)
1.4) 게임 씬을 껐다가 킨다.
1.5) P키를 누르고 로그 확인 (1.2와 같은 로그가 나오면 성공.)
2) DeleteSpecificData로 setting data 개별 삭제 기능 확인(setting이 아닌 Goods를 넣으면 유저의 데이터가 삭제되는 것을 확인 가능하다.)
2.1) P키를 눌러 로그 sound = true 확인.
2.2)A키를 눌러 로그 sound = false 확인.
2.3) D키를 눌러 변경된 내용이 사라지는 로그 확인.
2.4) P키를 눌러 초기화되어 sound = true 확인.
3) OnApplicationQuit으로 게임 나갈 시 자동 저장 여부 확인
3.1) Space키를 눌러 Gold 수치를 증거. (Gold : 1000)
3.2) 아무 키를 누르지 않은 상태로 유니티 플레이 버튼을 눌러 게임을 끈다.
3.3) 다시 게임을 킨다.
3.4) P키를 눌러 3.1의 Gold값과 같으지 확인. 같으면 성공. (Gold : 1000)
4) OnApplicationPause로 게임이 앱의 백그라운드로 내려갈 때 저장여부 확인
4.1) 유니티 메뉴에서 Edit > Project Settings > Player로 간다.
4.2) Resolution and Presentation 탭을 연다.
4.3) Run In Background 체크가 해제되어 있는지 확인.
(체크되어 있는 경우 : 창 밖을 클릭해도 게임이 계속 돌아가기 때문에 Pause 조건이 통하지 않는다.)
(해제되어 있는 경우 : 창 밖을 클릭하는 순간 유니티가 Pause상태가 되면서 OnApplicationPause(true)를 호출한다.)
4.4) 플레이 버튼을 눌러 게임을 시작.
4.5) Space 키로 데이터를 변경. (Gold 증가)
4.6) 마우스로 유니티 에디터가 아닌 다른 프로그램을 클릭해 포커스를 잃게 만든다.
4.7) 유니티 콘솔 로그를 확인. (UserDataManager : Save All Data)
직접적인 데이터 확인을 원하면 Tools > Open Save Folder를 확인해 UserGoodsData.json 내부 Gold값을 확인한다.
테스트 후 이전 상태로 되돌리기 위해 R키를 눌러 초기화한다.
위와 같은 방법으로 테스트한 뒤, 기능들이 잘 적용되어서 커밋하고 PR을 만들었다.
5. 후기
어떤 기능을 구현할 건지 기능명세를 처음에 명확하게 쓰고 시작하면 깔끔하지만, 또 어디까지를 확장의 범위로 잡을건지 어디까지는 곡 구현되어야 하는지 해당 기준을 잡고 판단하는 부분이 어려웠다. 해당 부분은 많이 만들어봐야 어느 정도 작업양과 시간에 대한 감이 오지 않을까 싶다.