본문 바로가기
Development/Internship

[멋사 로켓단 인턴쉽] 17일차 - 게임 완성도 올리기

by Mobics 2025. 8. 29.

목차


    멋쟁이사자처럼 로켓단 인턴쉽

    25.08.29

    회의록

    >> 안건

    • 업무 업데이트
      • 질문 혹은 피드백 요청
    • 주요 안건
      • 앱 출시 작업 전 간단한 QA 및 밸런싱 논의
    • 향후 마일스톤
      • 앱 출시 및 안정화 작업

    >> 회의 내용

    • 게임을 플레이 해보며 굳이 안 할 거 같은 것까지 건드려서 버그나 이슈 찾기
    • 모바일 환경 플레이 체감 밸런스 확인
      • 밸런스 쪽 추가의견 없음.
    • 출시 전 기능 전체 점검
    • 추가 디테일 점검

     

    버그 찾기

    : 이제 게임이 거의 다 완성됐고, 게임 등급 심의가 확정되고 출시되기를 기다리고 있다. 하지만 인턴 기간이 남았으므로 자잘한 버그를 찾고 디테일을 더 살리려고 한다.

     

    >> 찾은 버그

    : 지난 번에 만든 앱이 중단됐을 때 Audio가 Mute되는 기능이 IOS 환경에서는 잘 작동했지만 안드로이드 기능에서는 작동하지 않는 버그

    --> 현재 만든 게임이 모바일용으로 빌드한 것이 아니라 WebGL로 빌드한 다음 그걸 웹에 띄우고 토스 API를 통해 토스 앱 안에서 실행하는 구조라서 OnApplicationPause()가 제대로 동작하지 않았을 가능성이 있다.

     

    ▶ 토스 API와 연동하신 팀원 분께서 직접 해결하시기로 결정

     

    결과창에서 바로 재시작 할 수 있도록

    : 현재 게임이 끝났을 때 무조건 타이틀 화면으로 갔다가 게임을 시작해야하는데, 결과창에 재시작 버튼을 추가하여 바로 재시작 할 수 있도록 구현

    --> 일시정지 상태에서 게임을 재시작 하는 것과 동일하게 구현하되, "퇴근하기" 버튼과 같이 점수가 다 나오기 전에는 비활성화 했다가 점수가 다 나오고 나서 활성화 되도록 구현

     

    >> 작성한 코드

    - ResultUIController.cs

    <hide/>
    [SerializeField] private Button _retryButton;
    
    void Awake()
    {
        _quitButton.onClick.AddListener(OnClickQuitButton);
        _retryButton.onClick.AddListener(OnClickRetryButton);
        errorCheckImage.gameObject.SetActive(false);
    }
    
    public void InitResultItem(GameResultData resultData)
    {
        // 점수 보내기
        //해당기능에서는 점수를 string 타입으로 받음. 임시로 정수 형변환을 시켰지만
        //추후 반올림같은 로직을 넣는다면 그렇게 한 결과값을 인수로 넣도록 수정할 것.
        NetworkManager.Instance.SendScore((int)resultData.Score);
        
        // FadeOut Panel 초기화
        fadeOutCanvasGroup.alpha = 0;
        
        // 퇴근 및 재시작 버튼 비활성화
        _quitButton.gameObject.SetActive(false);
        _retryButton.gameObject.SetActive(false);
        
        // New Record 이미지 비활성화
        newRecordImage.gameObject.SetActive(false);
        
        // BGM 볼륨을 절반으로 설정
        AudioManager.Instance.BGM.SetBGMVolumeHalf();
        
        // 처음에는 0으로 초기화
        dayText.text = "0";
        maxComboText.text = "0";
        scoreText.text = "0";
        
        // TODO: 유저의 최고기록 불러오기 (임시: PlayerPrefs)
        float bestScore = PlayerPrefs.GetFloat("BestScore", 0f);
        
        AudioManager.Instance.SFX.PlayScoreCalculating();
        Sequence seq = DOTween.Sequence();
        
        // Day Count Up
        seq.Append(DOTween.To(() => 0, x => dayText.text = x.ToString() + "일", resultData.Day, 1f)
            .OnComplete(() =>
            {
                AudioManager.Instance.SFX.PlayScoreCalculated();
            }));
        seq.AppendInterval(0.2f);
        
        // MaxCombo Count Up
        seq.Append(DOTween.To(() => 0, x => maxComboText.text = x.ToString(), resultData.MaxCombo, 1f)
            .OnComplete(() =>
            {
                AudioManager.Instance.SFX.PlayScoreCalculated();
            }));
        seq.AppendInterval(0.2f);
    
        // Score Count Up
        seq.Append(DOTween.To(() => 0, x => scoreText.text = x.ToString("N0"), resultData.Score, 1.5f)
            .OnComplete(() =>
            {
                AudioManager.Instance.SFX.StopScoreCalculating();
                AudioManager.Instance.SFX.PlayScoreCalculated();
                // 퇴근 및 재시작 버튼 활성화
                _quitButton.gameObject.SetActive(true);
                _retryButton.gameObject.SetActive(true);
                
                // New Record 체크
                if (resultData.Score > bestScore)
                {
                    PlayerPrefs.SetFloat("BestScore", resultData.Score);
                    PlayerPrefs.Save();
                    ShowNewRecordEffect();
                }
            }));
    }
    
    public void OnClickRetryButton()
    {
        GameManager.Instance.ResumeGame();
        GameManager.Instance.inGameController.Dispose();
        GameManager.Instance.inGameController.UseRetry();
        GameManager.Instance.inGameController.SkipResultUI();
        GameManager.Instance.inGameController.QuitGame();
        ClosePopup();
    }

     

    >> 참고 코드

    - GameManager.cs

    <hide/>
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using static Constants;
    
    public class GameManager : Singleton<GameManager>
    {
        public InGameController inGameController;
        public ObstacleClearEffect obstacleClearEffect;
        
        /// <summary>
        /// 게임은 상태패턴으로 관리됩니다. Title, InGame, Pause 세가지로 관리됩니다.
        /// IGameState 인터페이스를 기반으로 만들어졌으며, 각 상태에 진입(OnEnter)할 때 씬을 로드합니다.
        /// GameState는 ChangeGameState를 통해 변경합니다.
        /// </summary>
        private GameState _previousState;
        private GameState _currentState;
        //public GameState CurrentGameState => _currentState;
        private Dictionary<GameState, IGameState> _states = new Dictionary<GameState, IGameState>();
        public Action<GameState> GameStateChanged;
    
        //일시정지 관리.
        public bool _isPaused;
        
        protected override void Initialize()
        {
            //Initialize
            _states[GameState.Title] = new TitleState();
            _states[GameState.InGame] = new InGameState();
            _states[GameState.Pause] = new PauseState();
            
            inGameController = new InGameController();
            obstacleClearEffect = new ObstacleClearEffect();
            obstacleClearEffect.Initialize();
            
            _isPaused = false;
            
            SceneManager.sceneLoaded += OnSceneLoaded;
        }
        
        private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
        {
            SceneManager.sceneLoaded -= OnSceneLoaded;
            _= LoadGameSystem();
        }
    
        //최초 실행시 초기화 진행
        private async Task LoadGameSystem()
        {
            while (UIManager.Instance.popupUIController.introUIController == null)
            {
                await Task.Yield();
            }
            UIManager.Instance.popupUIController.introUIController.InitUI();
            //Addressable 데이터 로드
            await obstacleClearEffect.LoadSprites();
            await UIManager.Instance.popupUIController.pauseUIController.LoadSprites();
            await UIManager.Instance.titleUIController.subMenuUIController.LoadSprites();
            await UIManager.Instance.inGameUIController.interactionUIController.LoadSprites();
            StartCoroutine(LoadEndInitGame());
        }
    
        private IEnumerator LoadEndInitGame()
        {
            yield return StartCoroutine(
                UIManager.Instance.popupUIController.introUIController.InitIntroUI());
            yield return StartCoroutine(inGameController.Initialize());
            ChangeGameState(GameState.Title);
            yield return null;
        }
        
        private void Update()
        {
            if (_currentState != GameState.None)
            {
                _states[_currentState].OnUpdate();
            }
            
            // //테스트용 입력
            // if (Input.GetKeyDown(KeyCode.Alpha1))
            // {
            //     GoToInGame();
            // }
            // else if (Input.GetKeyDown(KeyCode.Alpha2))
            // {
            //     //게임오버 시키기
            //     inGameController.QuitGame();
            //     //ReturnToTitle();
            // }
            // else if (Input.GetKeyDown(KeyCode.Alpha3))
            // {
            //     PauseGame();
            // }
            // else if (Input.GetKeyDown(KeyCode.Alpha4))
            // {
            //     ResumeGame();
            // }
        }
        
        ///게임을 시작합니다.
        public void GoToInGame()
        {
            ChangeGameState(GameState.InGame);
        }
    
        ///타이틀로 돌아갑니다.
        public void ReturnToTitle()
        {
            ChangeGameState(GameState.Title);
        }
    
        ///게임을 일시정지 합니다.
        public void PauseGame()
        {
            GetDocumentController()._isClickable = false;
            
            if (_isPaused)
            {
                Debug.LogWarning("Game is already paused");
                return;
            }
            
            ChangeGameState(GameState.Pause);
            _isPaused = true;
        }
    
        ///게임을 재개 합니다.
        public void ResumeGame()
        {
            if (!_isPaused)
            {
                Debug.LogWarning("Game is not paused");
                return;
            }
            
            ChangeGameState(GameState.Pause, true);
            _isPaused = false;
        }
    
        ///게임의 상태를 변경합니다.
        public void ChangeGameState(GameState newGameState, bool resume = false)
        {
            //기존 State 종료
            if (_currentState != GameState.None)
            {
                _states[_currentState].OnExit();
            }
    
            //일시정지 해제 시
            if (_currentState == GameState.Pause && newGameState == GameState.Pause && resume)
            {
                _currentState = _previousState;
            }
            else//새 State로 전환
            {
                _previousState = _currentState;
                _currentState = newGameState;
                
                _states[_currentState].OnEnter();
            }
            
            //State전환 후 실행할 Action이 있으면 실행
            GameStateChanged?.Invoke(_currentState);
        }
        
        ///TimeController가 필요할땐 이 함수를 쓰시면 됩니다.
        public TimeController GetTimeController()
        {
            return inGameController.timeController != null ? inGameController.timeController : null;
        }
        
        ///DocumentController가 필요할땐 이 함수를 쓰시면 됩니다.
        public DocumentController GetDocumentController()
        {
            return inGameController.docController != null ? inGameController.docController : null;
        }
    
        ///Classification이 필요할땐 이 함수를 쓰시면 됩니다.
        public Classification GetClassification()
        {
            return inGameController.classification != null ? inGameController.classification : null;
        }
    
        public GameState GetGameState()
        {
            return _currentState;
        }
    
        ///일시정지(백그라운드 상태) 되었을 때
        private void OnApplicationPause(bool pauseStatus)
        {
            //Debug.Log("OnApplicationPause: " + pauseStatus);
        }
    
        ///게임이 종료되었을 때
        private void OnApplicationQuit()
        {
            //Debug.Log("OnApplicationQuit");
        }
    
        public new void OnDestroy()
        {
            obstacleClearEffect.ClearSprites();//List에 로드한 스프라이트 해제
            base.OnDestroy();
        }
    }

     

    - InGameController.cs

    • Dispose() : _gameFinished가 true가 되면 EndGame()이 호출되면서 게임이 종료됨
    • UseRetry() : 게임오버 연출을 스킵하고 곧바로 새 게임을 위한 초기화 후, 게임을 시작한다.
    • SkipResultUI() : 결과창 UI를 스킵한다.
    <hide/>
    using System.Collections;
    using UnityEngine;
    using Object = UnityEngine.Object;
    
    //인게임 주요 로직들을 제어합니다.
    public class InGameController
    {
        public TimeController timeController;
        public DocumentController docController;
        public Classification classification;
        
        public bool Initialized;
        
        private bool _initComplete;
        private bool _gameStarted;
        private bool _gameFinished;
        private bool _quitGame;
        private bool _skipResultUI;
        private bool _useRetry;
        private bool _newRecordOn;
        private bool _endAdmob;
        
        
        public IEnumerator Initialize()
        {
            //TODO: 게임 실행시 초기화 할 로직
            //classification = new Classification();
            
            //씬 내 타이머, 문서생성 오브젝트 찾기
            if (timeController == null)
            {
                yield return new WaitUntil(() => timeController = Object.FindObjectOfType<TimeController>());
            }
            if (docController == null)
            {
                yield return new WaitUntil(() => docController = Object.FindObjectOfType<DocumentController>());
            }
            if(classification == null)
            {
                yield return new WaitUntil(() => classification = Object.FindObjectOfType<Classification>());
                classification.Initialize();
            }
            
            Initialized = true;
        }
        
        //게임이 시작되면 이 시퀀스를 통해 진행됩니다.
        public IEnumerator RunSequence()
        {
            //실행 필수 초기화 진행 체크
            yield return new WaitUntil(() => Initialized);
            
            //게임 시퀀스 실행
            yield return new WaitUntil(() => _initComplete);
            yield return StartGame();
    
            yield return new WaitUntil(() => _gameFinished);
            yield return EndGame();
        }
        
        //게임 시작 전 초기화
        public IEnumerator SetInitGame()
        {
            //실행 필수 초기화 완료전 까지 대기
            yield return new WaitUntil(() => Initialized);
            
            //TODO: 게임 시작시 초기화 할 로직
            _initComplete = false;
            
            _gameStarted = false;
            _gameFinished = false;
            _quitGame = false;
            _skipResultUI = false;
            _useRetry = false;
            _newRecordOn = false;
            _endAdmob = false;
            
            // BGM 초기화
            AudioManager.Instance.BGM.SetBGMVolumeMax();    // 볼륨을 최대로
            AudioManager.Instance.BGM.SetBGMSpeedNormal();  // 배속을 기본으로
    
            //타이머 초기화
            timeController.InitTimeController();
            
            //서류 풀 초기화
            docController.ReloadDocument(true);
            
            //classification.InitScore();
            GameManager.Instance.GetClassification().InitScore();
            _initComplete = true;
            
            // NewRecord 이미지 위치 초기화
            InGameUIController.Instance.scoreUIController.InitNewRecordImage();
            
            // 시계 프레임 색상 초기화
            InGameUIController.Instance.clockUIController.InitClockFrameColor();
        }
    
        
        //게임 시작
        public IEnumerator StartGame()
        {
            _gameStarted = true;
            //인게임UI 보이기
            UIManager.Instance.inGameUIController.ShowTimeUI();
            UIManager.Instance.inGameUIController.ShowInteractionUI();
            UIManager.Instance.inGameUIController.ShowScoreUI();
            UIManager.Instance.inGameUIController.ShowFeverUI();
            UIManager.Instance.inGameUIController.ShowBackgroundUI();
            UIManager.Instance.inGameUIController.ShowClockUI();
            UIManager.Instance.inGameUIController.ShowClassificationUI();
            UIManager.Instance.inGameUIController.ShowWaitThreeSecondsUI();
            UIManager.Instance.inGameUIController.ShowDifficultyUpEffectUI();
            
            // 인게임 BGM 재생
            AudioManager.Instance.BGM.PlayBGMByState(GameManager.Instance.GetGameState());
            
            //321
            yield return UIManager.Instance.StartCoroutine(
                UIManager.Instance.inGameUIController.waitThreeSecondsUI.WaitThreeSeconds()
            );
    
            //난이도 상승시점 모니터링 시작
            DifficultyManager.Instance.InitLevelMonitor();
    
            //타이머, 문서 생성 시작.
            timeController.StartRunningTimer();
            docController.InitDocuments();
            
            while (!_gameFinished)
            {
                yield return null;
            }
        }
    
        //게임 종료. 결과 보고
        public IEnumerator EndGame()
        {
            //TODO: 게임 끝낼 시 실행할 로직
            
            //시간 정지
            timeController.StopTime();
            
            docController._isClickable = false;
            
            // 시간 부족 SFX 반복 재생 중지
            AudioManager.Instance.SFX.StopTimeOutAlert();
            
            //ex.게임오버 연출, 결과창UI등
            
            var popupController = UIManager.Instance.popupUIController;
    
            if (!_useRetry)//재시작 활성화 시 엔드연출 스킵
            {
                //게임오버
                var gameOverUI = popupController.gameOverUIController;
                yield return gameOverUI.ShowSequence(); //게임오버 연출동안 딜레이
            }
            
            
            if (!_skipResultUI)//필요 시 스킵
            {
                //결과창
                var resultUI = popupController.resultUIController;
                popupController.ShowResultUI();
                resultUI.InitResultItem(new GameResultData(
                    timeController._day,
                    GameManager.Instance.GetClassification().GetMaxCombo(),
                    GameManager.Instance.GetClassification().GetScore()));
            }
            
            //게임이 끝난 후, 바로 돌아가지 않고 대기.
            while (!_quitGame)
            {
                yield return null;
            }
            
            //결과창이 떠야 게임 한판을 완료했다는 것이므로
            if (!_skipResultUI)
            {
                //광고호출
                NetworkManager.Instance.ShowAd();
    
                //광고 끝나기 전까지 대기
                while (!_endAdmob)
                {
                    yield return null;
                }
            
                //광고 제어 변수 초기화
                _endAdmob = false;
            
                //다음 광고 로드
                NetworkManager.Instance.LoadAd();
            }  
            
            //초기화.
            _initComplete = false;
            _skipResultUI = false;
            
            //재시작 필요 시.
            if (_useRetry)
            {
                //새 게임 코루틴 활성화
                yield return GameManager.Instance.StartCoroutine(SetInitGame());
                GameManager.Instance.StartCoroutine(RunSequence());
                
                //재시작을 위해 타이틀로 복귀하지 않고 기존 코루틴을 중단한다.
                yield break;
            }
            
            // BGM 초기화
            AudioManager.Instance.BGM.SetBGMVolumeMax();    // 볼륨을 최대로
            AudioManager.Instance.BGM.SetBGMSpeedNormal();  // 배속을 기본으로
            
            //인게임 UI 닫기
            UIManager.Instance.inGameUIController.HideInGameUI();
            UIManager.Instance.inGameUIController.HideTimeUI();
            UIManager.Instance.inGameUIController.HideInteractionUI();
            UIManager.Instance.inGameUIController.HideScoreUI();
            UIManager.Instance.inGameUIController.HideFeverUI();
            UIManager.Instance.inGameUIController.HideBackgroundUI();
            UIManager.Instance.inGameUIController.HideClockUI();
            UIManager.Instance.inGameUIController.HideClassificationUI();
            UIManager.Instance.inGameUIController.HideWaitThreeSecondsUI();
            UIManager.Instance.inGameUIController.HideDifficultyUpEffectUI();
            
            //타이틀 씬으로 복귀
            GameManager.Instance.ReturnToTitle();
        }
        
        // New Record 체크 및 연출 재생
        public void CheckNewRecord(float currentScore)
        {
            if (_newRecordOn) return;   // 이미 NewRecord 연출이 재생됐으면 넘기도록
    
            float bestScore = PlayerPrefs.GetFloat("BestScore", 0f);
            if (currentScore > bestScore)
            {
                // New Record 연출
                InGameUIController.Instance.scoreUIController.ShowNewRecordImage();
                AudioManager.Instance.SFX.PlayNewRecordScoreBar();
    
                _newRecordOn = true;
            }
        }
    
        ///게임 끝내기, 호출 시 진행중인 게임이 끝납니다.
        public void Dispose()
        {
            //게임이 시작했을때만
            if(_gameStarted) _gameFinished = true;
        }
    
        ///게임 끝난 후, 호출 시 타이틀로 돌아갑니다.
        public void QuitGame()
        {
            if(_gameFinished) _quitGame = true;
        }
    
        ///결과 UI 스킵필요 시 게임 종료 전 호출
        public void SkipResultUI()
        {
            _skipResultUI = true;
        }
    
        //재시작 필요 시 먼저 호출.
        public void UseRetry()
        {
            _useRetry = true;
        }
    
        public bool GetGameStarted()
        {
            return _gameStarted;
        }
    
        public void EndAdMob()
        {
            _endAdmob = true;
        }
    }

     

    └ 최종 코드

    >> ResultUIController.cs

    <hide/>
    using System.Collections;
    using System.Collections.Generic;
    using TMPro;
    using UnityEngine;
    using UnityEngine.UI;
    using DG.Tweening;
    
    public class ResultUIController : PopupController
    {
        [SerializeField] private RectTransform _rectTransform;
        [SerializeField] private CanvasGroup _canvasGroup;
        [SerializeField] private Button _quitButton;
        [SerializeField] private Button _retryButton;
    
        [SerializeField] private TMP_Text dayText;
        [SerializeField] private TMP_Text maxComboText;
        [SerializeField] private TMP_Text scoreText;
        
        [SerializeField] private Image newRecordImage;
        [SerializeField] private CanvasGroup fadeOutCanvasGroup;
        
        public Image errorCheckImage;
    
        void Awake()
        {
            _quitButton.onClick.AddListener(OnClickQuitButton);
            _retryButton.onClick.AddListener(OnClickRetryButton);
            errorCheckImage.gameObject.SetActive(false);
        }
        
        public void ShowPopup()
        {
            base.ShowPopup(gameObject);
        }
        
        public void ClosePopup()
        {
            base.ClosePopup(gameObject);
            errorCheckImage.gameObject.SetActive(false);
        }
        
        public void InitResultItem(GameResultData resultData)
        {
            // 점수 보내기
            //해당기능에서는 점수를 string 타입으로 받음. 임시로 정수 형변환을 시켰지만
            //추후 반올림같은 로직을 넣는다면 그렇게 한 결과값을 인수로 넣도록 수정할 것.
            NetworkManager.Instance.SendScore((int)resultData.Score);
            
            // FadeOut Panel 초기화
            fadeOutCanvasGroup.alpha = 0;
            
            // 퇴근 및 재시작 버튼 비활성화
            _quitButton.gameObject.SetActive(false);
            _retryButton.gameObject.SetActive(false);
            
            // New Record 이미지 비활성화
            newRecordImage.gameObject.SetActive(false);
            
            // BGM 볼륨을 절반으로 설정
            AudioManager.Instance.BGM.SetBGMVolumeHalf();
            
            // 처음에는 0으로 초기화
            dayText.text = "0";
            maxComboText.text = "0";
            scoreText.text = "0";
            
            // TODO: 유저의 최고기록 불러오기 (임시: PlayerPrefs)
            float bestScore = PlayerPrefs.GetFloat("BestScore", 0f);
            
            AudioManager.Instance.SFX.PlayScoreCalculating();
            Sequence seq = DOTween.Sequence();
            
            // Day Count Up
            seq.Append(DOTween.To(() => 0, x => dayText.text = x.ToString() + "일", resultData.Day, 1f)
                .OnComplete(() =>
                {
                    AudioManager.Instance.SFX.PlayScoreCalculated();
                }));
            seq.AppendInterval(0.2f);
            
            // MaxCombo Count Up
            seq.Append(DOTween.To(() => 0, x => maxComboText.text = x.ToString(), resultData.MaxCombo, 1f)
                .OnComplete(() =>
                {
                    AudioManager.Instance.SFX.PlayScoreCalculated();
                }));
            seq.AppendInterval(0.2f);
    
            // Score Count Up
            seq.Append(DOTween.To(() => 0, x => scoreText.text = x.ToString("N0"), resultData.Score, 1.5f)
                .OnComplete(() =>
                {
                    AudioManager.Instance.SFX.StopScoreCalculating();
                    AudioManager.Instance.SFX.PlayScoreCalculated();
                    // 퇴근 및 재시작 버튼 활성화
                    _quitButton.gameObject.SetActive(true);
                    _retryButton.gameObject.SetActive(true);
                    
                    // New Record 체크
                    if (resultData.Score > bestScore)
                    {
                        PlayerPrefs.SetFloat("BestScore", resultData.Score);
                        PlayerPrefs.Save();
                        ShowNewRecordEffect();
                    }
                }));
        }
    
        // New Record 시, 효과
        private void ShowNewRecordEffect()
        {
            newRecordImage.gameObject.SetActive(true);
            
            // 초기화 (작고 안 보이는 상태)
            newRecordImage.color = new Color(1f, 1f, 1f, 0f);
            newRecordImage.rectTransform.localScale = Vector3.zero * 0.8f;
            
            Sequence seq = DOTween.Sequence();
            // Fade In + Scale Up
            seq.Append(newRecordImage.DOFade(1f, 0.5f));
            seq.Join(newRecordImage.rectTransform.DOScale(1.2f, 0.3f).SetEase(Ease.OutBack));
            // SFX 재생
            seq.AppendCallback(() =>
            {
                AudioManager.Instance.SFX.PlayNewRecordResult();
            });
            // 살짝 튕기면서 원래 크기로
            seq.Append(newRecordImage.rectTransform.DOScale(1f, 0.2f).SetEase(Ease.OutBack));
            // 착! 강조
            seq.Append(newRecordImage.rectTransform.DOScale(0.95f, 0.1f).SetEase(Ease.InQuad));
            seq.Append(newRecordImage.rectTransform.DOScale(1f, 0.15f).SetEase(Ease.OutQuad));
        }
    
        public void OnClickQuitButton()
        {
            // Title로 가기 전, FadeOut
            fadeOutCanvasGroup.DOFade(1f, 1f)
                .OnComplete(() =>
                {
                    ClosePopup();
                    GameManager.Instance.ResumeGame();
                    GameManager.Instance.inGameController.QuitGame();
                });
        }
    
        public void OnClickRetryButton()
        {
            GameManager.Instance.ResumeGame();
            GameManager.Instance.inGameController.Dispose();
            GameManager.Instance.inGameController.UseRetry();
            GameManager.Instance.inGameController.SkipResultUI();
            GameManager.Instance.inGameController.QuitGame();
            ClosePopup();
        }
    }