시스템 구현/Unity: Android - 푸시 알림 시스템

알람 기능 제작기 (1)

SharpSteamedBread 2026. 2. 25. 15:36

캐릭터와의 교류를 폭넓게 구현하고 지원하는 게임이 많아지고 있다. 더 나아가 하나의 게임을 즐기는 방법이 되기도 한다. 물론 나도 질 수 없다. 플레이어가 알람을 해주길 원하는 캐릭터와 알람 시간대를 설정하면 해당 시간에 맞게 알람이 울리는 기능을 제작해 본다.

 

1. 시간 버튼을 눌러 원하는 시간을 설정하도록 한다.

2. 오전/오후 버튼을 눌러 반나절을 계산한다.

3. 스크롤바로 몇분인지를 설정하도록 한다.

4. 시간 계산을 눌러 알람 시간을 확정한다.

 

 

public class ManageTime
{
    private DateTime setAlarmTime;

    public string dayAndNight;
    public int hour24;
    public int year;
    public int month;
    public int day;

    public int hour;
    public int minute;
    public int second;

    public DateTime SetAlarmTime
    {
        get { return setAlarmTime; }
        set
        {
            setAlarmTime = value;
        }
    }

기본적으로, DateTime 구조체를 이용하여 시간을 계산한다. ManageTime에서 알람의 설정값을 담는 변수를 관리한다.

 

public class SetAlarm : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI objCurrentText;
    [SerializeField] private GameObject objAlarmUI;

    private DateTime todayDateTime;

    [Space(20)]
    [Header("Date")]
    //Now 대신 UtcNow를 사용해야 지역별 시간차를 이용하는 버그를 막을 수 있다.
    //AddHours(9): UTC+9
    DateTime today = DateTime.UtcNow.AddHours(9);

    private void OnEnable()
    {
        SetCurrentTime();

        todayDateTime = new DateTime(today.Year, today.Month, today.Day, today.Hour, today.Minute, today.Second);
        alarmTime.SetAlarmTime = todayDateTime;
    }

    /// <summary>
    /// 알람 UI가 뜨자마자 현재 시간이랑 동기화해서 띄우기
    /// </summary>
    private void SetCurrentTime()
    {
        //텍스트에는 표현되지 않는 시간 동기화
        alarmTime.year = today.Year;
        alarmTime.month = today.Month;
        alarmTime.day = today.Day;
        alarmTime.minute = today.Minute;

        //현재시각에 따른 시간, 분 동기화(UTC +9 기준)
        if (today.Hour <= 12)
        {
            alarmTime.hour = today.Hour;
            alarmTime.dayAndNight = "오전";
        }

        else if (today.Hour >= 12)
        {
            alarmTime.hour = today.Hour - 12;
            alarmTime.dayAndNight = "오후";
        }

        //시간 텍스트 동기화
        InitTimeText();

        //분 세팅(값이 고정되니까)
        objScrollbar.value = today.Minute / 60f;
    }
    
        private void InitTimeText()
    {
        //맨 위 텍스트 동기화
        objCurrentText.text = $"{alarmTime.dayAndNight} {alarmTime.hour}시 {alarmTime.minute}분";

        alarmTime.SetAlarmTime = new DateTime(today.Year, today.Month, today.Day, alarmTime.hour24, alarmTime.minute, 0);
    }

한국 시간에 따르므로 UTC +9가 기준이다. Utc.Now를 이용하여 지역별 시간차를 이용하는 버그를 방지한다. 우선 UI가 활성화되면, 상위 텍스트에 현재 시간을 동기화하여 띄운다.

 

    /// <summary>
    /// 시간 설정 버튼 누르면 버튼에 따른 값 동기화하기
    /// </summary>
    /// <param name="value"></param>
    public void SetHour(int value)
    {
        alarmTime.hour = value;

        //시간차를 계산할 때 현실 시간과 다르게 알람 시간은 12시간제이므로 이를 따로 치환해줘야 함
        alarmTime.hour24 = (alarmTime.dayAndNight == "오전") ? value : value + 12;
        
        //시간이 0~23까지의 범위이므로 24가 되면 오전 0시로 돌아감
        if (alarmTime.hour24 >= 24)
        {
            alarmTime.hour24 = 0;
            alarmTime.dayAndNight = "오전";
        }

        else if(alarmTime.hour24 == 12 && alarmTime.dayAndNight == "오전")
        {
            alarmTime.dayAndNight = "오후";
        }


        InitTimeText();
    }
    
    /// <summary>
    /// 오전/오후 설정 버튼 누르면 버튼에 따른 값 동기화하기
    /// 전체 알람 시간이 현재와 같거나 현재보다 이르면 미래 시간으로 바꿔야 한다.
    /// </summary>
    public void SetDayHalfTime(string meridiem)
    {
        alarmTime.dayAndNight = meridiem;

        if(meridiem == "오전" && alarmTime.hour24 > 12)
        {
            alarmTime.hour24 -= 12;
        }

        else if(meridiem == "오후" && alarmTime.hour24 <= 12)
        {
            alarmTime.hour24 += 12;
        }

        InitTimeText();
    }

DateTime.Hour는 24시간이다. 보편적으로 12시간제인 오전/오후를 사용하므로 24시간을 오전, 오후로 쪼갠다. 

 

    /// <summary>
    /// 스크롤바로 시간 설정하면 최종 값 동기화하기
    /// </summary>
    public void SetMinute()
    {
        alarmTime.minute = currMin;
        InitTimeText();
    }
    
        /// <summary>
    /// 분 스크롤바 위에 있는 텍스트 동기화 
    /// </summary>
    public void InitMinute()
    {
        //분 값-텍스트 동기화
        currMin = (int)(60 * objScrollbar.value);

        switch (currMin)
        {
            case 0:
                prevMin = currMin - 1;
                nextMin = currMin + 1;
                objPrevMin.enabled = false;
                break;

            case 60:
                prevMin = currMin - 1;
                nextMin = currMin + 1;
                objNextMin.enabled = false;
                break;

            default:
                prevMin = currMin - 1;
                nextMin = currMin + 1;

                objPrevMin.enabled = true;
                objNextMin.enabled = true;
                break;
        }

스크롤바를 이용하여 지정하는 분 설정의 경우, 00분부터 50분까지를 사용하므로 이에 맞게 스크롤바의 최소값과 최대값을 설정한다.

InitMinute()는 하나의 효과를 담당한다. 스크롤바를 스크롤하는 동안 그 위에 있는 텍스트들의 값을 가감한다.

SetMinute()는 플레이어가 값을 정하여 커서를 뗄 시, 해당값을 알람의 분 값으로 설정한다.

스크롤바에서 원하는 시간을 맞춘 다음 클릭 버튼을 떼면 그 값을 알람의 분 값으로 전달한다. 스크롤바를 좌우로 드래그하는 동안 (설정값 - 1). (설정값), (설정값 + 1)이 바뀐다.

 

Q. 엥? 그냥 On value changed 써도 되는 거 아닌가? 굳이 SetMinute()를 따로 나눌 필요가 있나?
A. UGUI는 자원을 많이 먹기 때문에 값이 세밀하다면 그만큼 많이 갱신해야 하므로, 자원 이용 측면에서 좋지 않다.UI 갱신을 Update문에서 되도록 사용하지 않는 것과 비슷하다. 

 

플레이어가 알람 시간을 전부 정했다면 다음 버튼을 눌러 설정한 알람이 잘 등록됐는지 확인할 것이다. 

그때 우리가 해야할 것은 

1. 플레이어가 설정한 알람 정보를 저장

2. 저장한 알람 정보를 UI로 출력

3. 게임을 껐다 켜도 데이터가 휘발되지 않도록 외부에 정보 저장

정도가 된다.

 

    public void CalculateTime()
    {
        todayDateTime = new DateTime(today.Year, today.Month, today.Day, today.Hour, today.Minute, today.Second);

        TimeSpan timeCalc = todayDateTime - alarmTime.SetAlarmTime;
        Debug.Log($"현재 시간: {todayDateTime}, 알람 시간: {alarmTime.SetAlarmTime}" +
                  $"\n시간차 계산: {timeCalc}");

        int timeCalcResult = DateTime.Compare(todayDateTime, alarmTime.SetAlarmTime);

        if(timeCalcResult < 0)
        {
            Debug.Log("알람 시간 미래 맞음!");

            alarmList.Add(alarmTime.SetAlarmTime);
            InstantiateAlarmUI();
        }

        else
        {
            alarmTime.SetAlarmTime = alarmTime.SetAlarmTime.AddDays(1); 
            Debug.Log($"알람 시간이 안맞아서 하루 늦췄어! {alarmTime.SetAlarmTime}");

            alarmList.Add(alarmTime.SetAlarmTime);
            InstantiateAlarmUI();
        }
    }

위에서 플레이어가 UI를 조작하면서 시, 분 정보를 입력하였다. 그 정보를 다시 DateTime 구조체로 재구성한다. 알람은 당연히 현재보다 과거 시간에 작동할 수 없으므로, 플레이어가 설정한 시간이 현재와 같거나 그보다 과거라면 하루를 미루어야 한다. (요일 설정은 아직 구현하지 않는다!)

DateTime 구조체끼리의 계산(시차 계산)은 TimeSpan 구조체를 이용한다. 하지만 시차 값이 아니라 값의 대소 관계만 확인하면 되므로 DateTime.Compare()를 사용하였다.

 

    /// <summary>
    /// 사용자가 알람 정보를 생성한대로 UI에 동기화한다.
    /// 리스트 Add는 반복문에 넣지 않는다.
    /// (넣는 순간 버튼을 누를 때 모든 리스트의 내용이 계속 UI로 생성된다)
    /// </summary>
    private void InstantiateAlarmUI()
    {
        //알람 박스 생성 및 리스트 추가
        GameObject clone = Instantiate(alarmListObj, parentScrollview.transform);
        alarmObjList.Add(clone);

        //알람 리스트(보기용) 및 정보 동기화
        showAlarmList.Add(alarmList[alarmList.Count - 1].ToString());
        for (int i = 0; i < alarmList.Count; i++)
        {
            alarmObjList[i].GetComponent<ReadAlarmOption>()._alarmTime = alarmList[i];
            showAlarmList[i] = alarmList[i].ToString();
        }
    }

CalculateTime()에서 생성한 DateTime 알람 정보를 UI로 생성한다. 구체적인 알람 개수는 정하지 않았지만, 알람이 하나만 설정할 수 있는 건 아니므로 리스트로 저장한다. 

그리고, DateTime 구조체는 유니티의 Inspector 창에서 뜨지 않는다. 단순히 기능이 잘 작동하는지 확인용으로 DateTime을 string 형식으로 치환한 리스트를 만들어 로그를 확인했다. 

 


이후 추가 구현:

1. 게임을 껐다 켜도 알람 설정 정보를 확인할 수 있도록 외부에 데이터를 저장하는 작업이 필요하다. json을 사용해 구현할 예정이다.

2. 주마다 반복, 월마다 반복, 일주일 내 특정 요일만 알람 켜기 등의 세부 기능을 구현해야 한다.

3. 제일 중요한!!! 알람 화면 그 자체를 구현해야 한다.