오늘은 특히 시간이라는 변수를 다뤄보면서 여러모로 시행착오를 겪었고, 배운 점이 많았다.
어제자로 찾아본 자료에서는 날짜를 string으로 저장해서 여러가지로 변환해서 쓰는 방법을 사용했다.
특히나 핵심이 되는 기능은 DateTime이라는 구조체이다.
의외로 이 생성자에 대해 공부를 안하고 시작하니 여러모로 헤맸었다. 기본적으로 새로운 개념을 배울 때 이런 부분을 미리 공부하면서 R&D를 시작하는 것이 좋을 것 같다.
DateTime은 다양한 오버로드의 함수를 제공하고, 대략적인 방식만 해도 이만큼 많다.
요 중에서도 예시로 내가 사용한 방식으로는 이런 방식으로 사용할 수도 있었다.
// 테스트용: 오늘 아침 6시, 가챠횟수 1회
DateTime todayReset = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 6, 0, 0);
또한 이런 DateTime 구조체를 Long으로 변환시키는 Ticks라는 것도 있으며, 연, 월, 일, 시간, 분 등 다양한 변수를 더하거나 뺄 수도 있으며, 시간차도 구할 수 있다.
// 테스트용 초기값: 11시간 전, 가챠 횟수 1회
_dailyAdGachaRewardInfo = new RewardInfo(DateTime.Now.AddHours(-11).Ticks, 1);
// 시간차
DateTime now = DateTime.Now;
DateTime lastTime = _dailyAdGachaRewardInfo.GetDateTime();
TimeSpan difference = now - lastTime;
지금은 로컬로만 테스트를 진행할 거지만, 추후에 파이어베이스와 연동하는 방식을 생각하면 시간을 어떻게 표현해야 하는가에 대해 깊게 고민했다. 그래서, 파이어베이스에서는 어차피 대부분의 데이터를 String으로 저장하니까, string으로 저장하는 게 좋지 않을까 생각했다. 참고한 예시의 경우에도 string으로 날짜를 계산하고 있었으니까.
하지만 시간을 표현하는 방법에서 string을 쓰면 가독성 면에서는 좋을 수도 있지만 문제점이 존재한다.
그건 바로 string 자체가 데이터가 무겁기 때문에 효율성이 좋지 않다는 것이다.
비교 대상인 long에 비해서 옮겨야 하는 데이터 양이 많고 변환 과정도 복잡하기 때문에 상대적으로 처리속도가 느려질 수밖에 없다는 점을 우려했다. 따라서 처음에는 string을 이용해서 날짜를 저장하고 업데이트하는 방식을 사용했지만, 이후에 방법을 변경하여 long을 사용하는 방식으로 바꿨다.
TimeManager의 역할은 다음과 같다.
시간과 획득 여부를 하나의 클래스 정보로 구현하는 것이 좋다고 판단했다. 따라서 아래와 같이 구현했다.
[Serializable]
public class RewardInfo
{
public long dateTicks; // DateTime을 Ticks로 저장
public int state; // 획득 여부 또는 스택 수
public RewardInfo(long dateTicks, int state)
{
this.dateTicks = dateTicks;
this.state = state;
}
public DateTime GetDateTime()
{
return new DateTime(dateTicks);
}
public void SetDateTime(DateTime dateTime)
{
dateTicks = dateTime.Ticks;
}
}
이와 같이 두 가지 데이터를 하나의 클래스로 정의하여 데이터베이스에 시간과 획득여부에 대한 값을 저장하고, load하거나 save하는 방식을 구현했다.
일일 가챠 보상, 그것도 특정 시에 초기화가 되어야 하므로 아래와 같이 판정하는 함수를 구현했다.
private bool IsDailyFreeGachaResetTime(DateTime date)
{
DateTime now = DateTime.Now;
if (now.Year > date.Year && now.Hour >= date.Hour) return true;
if (now.Year == date.Year && now.Month > date.Month && now.Hour >= date.Hour) return true;
if (now.Year == date.Year && now.Month == date.Month && now.Day > date.Day && now.Hour >= date.Hour) return true;
return false;
}
시간 판정은 간단해 보이지만 의외로 생각해야 할 점이 있다.
연 단위로 달라진 경우 월이나 일을 체크할 필요는 없다. 하지만 시의 경우 월말 몇시에 가챠를 뽑았고 1월 1일 몇시에 뽑기를 시도했는지 등이 중요하므로 연, 시간을 체크하고 true를 반환한다.
연은 같고 월 단위로 달라진 경우 일을 체크할 필요는 없다. 하지만 시는 체크하여 같은 연도, 다른 월, 시간을 체크해야 한다.
연, 월이 같고 일이 다른 경우, 시간을 비교하여 하루가 지났을 때를 판정 가능하다.
판정은 다음과 같이 진행된다.
/// <summary>
/// 12시간 단위 누적 스택 계산
/// </summary>
private bool IsDailyAdGachaResetTime(out int stack)
{
DateTime now = DateTime.Now;
DateTime lastTime = _dailyAdGachaRewardInfo.GetDateTime();
TimeSpan difference = now - lastTime;
stack = 0;
if (difference.TotalHours >= 12)
{
int stackCount = (int)(difference.TotalHours / 12);
stack = Mathf.Min(stackCount, 2); // 최대 2 스택
// 마지막 갱신 시간 이동
_dailyAdGachaRewardInfo.SetDateTime(lastTime.AddHours(12 * stack));
Debug.Log($"스택 증가: {stack}, 새로운 마지막 갱신 시간: {_dailyAdGachaRewardInfo.GetDateTime()}");
return true;
}
return false;
}
문제의 원인은 데이터의 변환 과정에서 함수로 들어가면서 저장이 되지 않는 문제가 발생한다는 것이었다.
이를 해결하기 위해서 따로 이 시간을 변수로 따로 두는 대신 클래스 데이터 자체를 직접 가지고 와서 계산하고 데이터를 바꾸는 방식으로 구현했다.