소소한 개발 공부

[Unity] 애니팡식 하트 충전 : 시간에 따른 값 충전 본문

개발/Unity

[Unity] 애니팡식 하트 충전 : 시간에 따른 값 충전

이내내 2021. 5. 14. 02:15
📝[참고]
[Unity | 유니티] 하트 충전 스크립트(로컬 디바이스 시간 기준)
출처: https://tenlie10.tistory.com/177 [유니티 게임 개발자]

 

애니팡처럼 시간에 따라 하트를 충전하는 기능을 만들고자 한다.게임을 껐을 때도 하트를 지속적으로 충전해야 한다.

 

1. 게임을 켰을 때 태초의 하트 값은 Max로 한다.- public 으로 된 love 값을 Max 로 설정해둔다.

- 혹은 Save된 AppQuitTime이 없는 태초의 상태에서 AppQuitTime은 1970년 1월1일로 되어 있으므로 (현재시간 - AppQuitTime)을 계산했을 때 방치 시간이 어마어마하게 많아 love은 자동으로 Max 값이 되어있을 것이다.

 

2. 게임을 껐을 때 게임을 나간 시간 (AppQuitTime)을 저장해 게임을 재시작했을 때 현재 시간과 계산하여 그 차를 게임을 켜지않고 방치한 시간으로 한다. (Now - AppQuitTime = 게임을 켜지않고 흐른 시간)

 

3. 게임을 켰을 때 흐르고 있었던 시간 (RechargeTime)에 방치한 시간을 더하고 하트를 충전하는데 걸리는 시간만큼 나눠 하트를 충전한다. 


 

하트 충전 스크립트를 만듦에 있어 사용하는 변수는 다음과 같다.

#region 
    public int loveAmount = 0;                  // 보유 애정 개수
    public int loveAmountMax = 5;               // 가질 수 있는 최대 애정 개수
    public int timeOfChargeLoveMax;      		// 애정 충전 시간

// UIManager에 빼는 등 따로 UI를 관리하는 스크립트가 가져가야 할 변수 
// 참조하는 값에 변경이 있을 때마다 바로바로 UI에 적용해준다.
    public Text textLove;						// 현재 애정 개수
    public Text textTimerOfLove;				// 애정이 충전되기까지 현재 남은 시간

    private DateTime m_AppQuitTime = new DateTime(1970, 1, 1).ToLocalTime();    // 유저 게임 이탈 시간 변수
    private Coroutine m_RechargeTimerCoroutine = null;
    private int m_RechargeRemainTime = 0;		// 현재 남은 시간
#endregion

#region - #endregion은 코드를 접을 수 있다. (코드 관리에 좋다)

 

게임을 켜거나 껐을 때 혹은 휴대폰 화면이 점멸되어 게임의 포커스 변경이 있을 때 게임 정보(하트, 게임 종료 시간, 현재 남은 시간)를 저장/로드해야한다.

// 휴대폰 화면 점멸될 때 등. 앱에 포커스가 있는지 없는지 변경이 있을 때 동작
    private void OnApplicationFocus(bool focusStatus) 
    {
        if (focusStatus == true)
        {
            LoadLoveInfo();
            LoadAppQuitTime();
            SetRechargeScheduler();
        }
        else
        {
            SaveLoveInfo();
            SaveAppQuitTime();
        }
    }

// 게임 종료 시 동작
    private void OnApplicationQuit() 
    {
        SaveLoveInfo();
        SaveAppQuitTime();
    }

OnApplicationFocus는 현재 앱에 포커스가 있는지 없는지 확인하는 Unity MonoBehaviour함수.

(휴대폰을 오래 방치해두어 화면이 어두워졌을 때=false, 게임을 완전히 끄지 않은 상태로 다른 앱을 실행할 때=false, 게임을 할 때=true)

 

게임을 켰을 때(focusStatus = true) 저장해둔 게임 정보를 로드한다.

게임을 끄거나 포커스가 나갈 때 지금까지 진행한 게임 정보를 저장한다.

 

    public bool SaveLoveInfo()
    {
        bool result = false;
        try
        {
            PlayerPrefs.SetInt("LoveAmount", loveAmount);
            PlayerPrefs.Save();
            result = true;
        }
        catch (System.Exception e)
        {
            Debug.LogError("SaveLoveInfo Failed (" + e.Message + ")");
        }
        return result;
    }

    public bool LoadLoveInfo()
    {
        bool result = false;
        try
        {
            if (PlayerPrefs.HasKey("LoveAmount"))
            {
                loveAmount = PlayerPrefs.GetInt("LoveAmount");
                if (loveAmount < 0)
                {
                    loveAmount = 0;
                }
            }
            else
            {
                loveAmount = loveAmountMax;
            }
            result = true;
        }
        catch (System.Exception e)
        {
            Debug.LogError("LoadLoveInfo Failed : " + e.Message + ")");
        }
        return result;
    }

    public bool SaveAppQuitTime()
    {
        bool result = false;
        try
        {
            var appQuitTime = DateTime.Now.ToLocalTime().ToBinary().ToString();
            PlayerPrefs.SetString("AppQuitTime", appQuitTime);

            var rechargeRemainTime = m_RechargeRemainTime;
            PlayerPrefs.SetInt("RechargeRemainTime", rechargeRemainTime);
            PlayerPrefs.Save();
            result = true;
        }
        catch (System.Exception e)
        {
            Debug.LogError("SaveAppQuitTime Failed (" + e.Message + ")");
        }
        return result;
    }

    public bool LoadAppQuitTime()
    {
        bool result = false;
        try
        {
            if (PlayerPrefs.HasKey("AppQuitTime"))
            {
                var appQuitTime = string.Empty;
                appQuitTime = PlayerPrefs.GetString("AppQuitTime");
                m_AppQuitTime = DateTime.FromBinary(Convert.ToInt64(appQuitTime));

                m_RechargeRemainTime = PlayerPrefs.GetInt("RechargeRemainTime");
            }
            result = true;
        }
        catch (System.Exception e)
        {
            Debug.LogError("LoadAppQuitTime Failed (" + e.Message + ")");   
        }
        return result;
    }

- PlayerPrefs로 게임 정보를 저장한다. PlayerPrefs는 특정 자료형만 저장가능하다는 단점이 있어 다양한 정보를 저장하려면 정보를 Serializable하게 만들어 파일로 저장한다. 

 

- 시간(DateTime)은 PlayerPrefs로 저장할 수 없으므로 Binary로 변경한 것을 string으로 변경해 SetString으로 저장한다. 

=>  로드할 때는 반대로... 

 

- try-catch문으로 에러를 처리한다.

 

	public void SetRechargeScheduler(Action onFinish = null)
    {
    	// 방치된 시간 계산하기
        // 애정은 얼마나 충전되었는가?
        if (m_RechargeTimerCoroutine != null)
        {// 현재 코루틴이 돌고 있는지...
            StopCoroutine(m_RechargeTimerCoroutine);
        }
        // (게임을 켠 현재 시간 - 게임을 껐던 시간) = 흐른 시간
        var timeDifferenceInSec = (int)((DateTime.Now.ToLocalTime() - m_AppQuitTime).TotalSeconds); // 방치한 동안 흐른 시간(초) 계산
        var loveToAdd = timeDifferenceInSec / timeOfChargeLoveMax;  // 몫 : 추가된 애정
        var remainTime = timeDifferenceInSec % timeOfChargeLoveMax; // 나머지 : 계산되고 남은 방치된 시간

        if (m_RechargeRemainTime <= remainTime)
        {// 게임을 끄기 전에 남아있던 시간이 흐른 시간 보다 작거나 같을 때
            if (m_RechargeRemainTime == remainTime)
                loveToAdd++;
            remainTime = timeOfChargeLoveMax + m_RechargeRemainTime - remainTime;
        }
        else
        {// 게임을 끄기 전 남아있던 시간이 방치한 시간보다 길 때
            remainTime = m_RechargeRemainTime - remainTime;
        }

        loveAmount += loveToAdd;
        if (loveAmount >= loveAmountMax)
        {// 애정 충전 완료
            loveAmount = loveAmountMax;
        }
        else
        {
            m_RechargeTimerCoroutine = StartCoroutine(DoRechargeTimer(remainTime, onFinish));
        }
        textLove.text = loveAmount.ToString();
    }

    private IEnumerator DoRechargeTimer(int remainTime, Action onFinish = null)
    {   // 게임 켠 동안 애정 충전하기
        if (remainTime <= 0)
        {// 공교롭게도 흐른시간이 0초일 때(애정을 충전하고 남은 시간이 0초)
        // 남은 시간을 애정 충전 시간 Max로 대입
            m_RechargeRemainTime = timeOfChargeLoveMax;
        }
        else
        {
            m_RechargeRemainTime = remainTime;
        }

        while (m_RechargeRemainTime > 0)
        {
            m_RechargeRemainTime -= 1;
            textTimerOfLove.text = (m_RechargeRemainTime / 60).ToString() + " : " + (m_RechargeRemainTime % 60).ToString();
            yield return new WaitForSeconds(1f);
        }

        loveAmount++;
        if (loveAmount >= loveAmountMax)
        {// 애정 충전 완료
            loveAmount = loveAmountMax;
            m_RechargeRemainTime = 0;
            m_RechargeTimerCoroutine = null;
        }
        else
        {
            m_RechargeTimerCoroutine = StartCoroutine(DoRechargeTimer(timeOfChargeLoveMax, onFinish));
        }

        textLove.text = loveAmount.ToString();
    }

예를 들어 

 

게임을 끄기 전 하트가 충전되기까지 남은 시간(저장된 m_RechargeRemainTime): 30초

 

게임을 방치한 뒤 흐른 시간(remainTime) : 20초

 

=> 게임을 켰을 때 남은 시간(사용할 remainTime DoRechargeTimer에서 m_RechargeRemainTime에 넣을 값)  : 10초

 

remainTime = m_RechargeRemainTime - remainTime

 

게임을 끄기 전 하트가 충전되기까지 남은 시간(저장된 m_RechargeRemainTime): 10초

게임을 방치한 뒤 흐른 시간(remainTime) : 20초

=> 게임을 켰을 때 남은 시간(사용할 remainTime DoRechargeTimer에서 m_RechargeRemainTime에 넣을 값)  : Max-10초

 

remainTime = timeOfChargeLoveMax + m_RechargeRemainTime - remainTime

 

    public void UseLove(Action onFinish = null)
    {
        if (loveAmount <= 0)
        {
            return;
        }
        loveAmount--;
        textLove.text = loveAmount.ToString();
        
        if (m_RechargeTimerCoroutine == null)
        {
            m_RechargeTimerCoroutine = StartCoroutine(DoRechargeTimer(timeOfChargeLoveMax));
        }
        if (onFinish != null)
        {
            onFinish();
        }
    }

하트를 사용했을 때 사용하는 함수.

하트를 사용하면 다시 하트를 채워야하므로 DoRechargeTimer 코루틴을 호출한다.


UI를 연결하고 timeOfCharLoveMax값을 설정하는 등 과정을 거치면 게임을 껐을 때도 하트를 충전할 수 있는 스크립트를 사용할 수 있다...