목차
멋쟁이사자처럼 로켓단 인턴쉽
25.08.25
회의록
>> 안건
- 업무 업데이트
- 질문 혹은 피드백 요청
- 주요 안건
- 게임 몰입감 증대를 위한 디테일 보강
- 몰입감을 해치는 요소, 버그 수정
- 2차 밸런싱 회의
- 향후 마일스톤
- 앱인토스 앱 등록, API 호출코드 적용
>> 회의 내용
- 밸런싱 추가 회의
: 지난 밸런싱 변경 내용
난이도 밸런싱 | 기존 | 1차 안 | 2차 안 |
1 DAY당 시간 | 120 → 30초 | 10초 | 10초 |
장애물 처리 난이도 증가 단위 | 3일 | 5일 | 5일 |
난이도 증가 최대 단계 | 무한대 | 3단계(1,5,10) (Max도달 1분40초) |
5단계(1,5,10,15,20) (Max도달 3분 20초) |
장애물 등장 확률 | Day마다 5%씩 증가 | 5%/20%/40% | 4%/10%/12%/20%/30% |
장애물 처리 난이도(처리횟수 증가) | 3일 | 5일 | 1/1/2/2/3 |
일과시간 실시간 감소주기 (1씩) | Time.delta | 1s/0.6s/0.2s | 1s/0.8s/0.6s/0.4s/0.2s |
서류 처리 시 일과시간 증감수치 | 증가: Day 감소: Day *5 |
증가: 1/3/5 감소: 3/7/12 |
증가: 1/1/2/2/3 감소: 3/5/7/9/12 |
- DAY당 시간, 처리 난이도 증가 단위는 그대로 유지
- 대신 최대 단계 확장
- 최고난이도 갔을때
- 어떻게 최대한 빨리 피버타임을 연속적으로 터트리냐가 생존의 핵심
- Speed Up시 체감 연출이 조금 부족한 듯함
- 난이도 증가 단계 별로 BGM 속도증가 추가하기.
- 인게임 QA 사항 최종 피드백 진행
Audio 토글 수정
: Audio 토글을 눌렀을 때 BGM을 정지/재생하는 것이 아니라 Mute/UnMute하도록
>> 수정한 코드
<hide/>
// BGM 음소거
private void MuteBGM()
{
if (_bgmSource != null)
{
_bgmSource.mute = true;
}
}
// BGM 음소거 해제
private void UnmuteBGM()
{
if (_bgmSource != null)
{
_bgmSource.mute = false;
}
}
// _isBGMOn값을 조정하고 그에 따라 BGM을 음소거 설정 및 해제
public void SetBGMOn(bool isBGMOn)
{
_isBGMOn = isBGMOn;
if (_isBGMOn) // 음소거 해제
{
UnmuteBGM();
}
else // 음소거
{
MuteBGM();
}
}
게임 결과창에서 BGM의 볼륨을 절반으로 설정
: 게임 결과창에서 BGM의 볼륨을 절반으로 설정하고, '퇴근하기' 버튼을 누르면 다시 BGM의 볼륨을 원상복귀
>> 작성한 코드
- BGMController.cs
<hide/>
// BGM 볼륨을 절반으로 설정
public void SetBGMVolumeHalf()
{
if (_bgmSource != null)
{
_bgmSource.volume = 0.5f;
}
}
// BGM 볼륨을 최대로 설정
public void SetBGMVolumeMax()
{
if (_bgmSource != null)
{
_bgmSource.volume = 1f;
}
}
- ResultUIController.cs
<hide/>
public void InitResultItem(GameResultData resultData)
{
// 점수 보내기
//해당기능에서는 점수를 string 타입으로 받음. 임시로 정수 형변환을 시켰지만
//추후 반올림같은 로직을 넣는다면 그렇게 한 결과값을 인수로 넣도록 수정할 것.
NetworkManager.Instance.SendScore((int)resultData.Score);
// FadeOut Panel 초기화
fadeOutCanvasGroup.alpha = 0;
// 퇴근 버튼 비활성화
_quitButton.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);
Sequence seq = DOTween.Sequence();
// Day Count Up
seq.Append(DOTween.To(() => 0, x => dayText.text = x.ToString() + "일", resultData.Day, 1f));
seq.AppendInterval(0.2f);
// MaxCombo Count Up
seq.Append(DOTween.To(() => 0, x => maxComboText.text = x.ToString(), resultData.MaxCombo, 1f));
seq.AppendInterval(0.2f);
// Score Count Up
seq.Append(DOTween.To(() => 0, x => scoreText.text = x.ToString("N0"), resultData.Score, 1.5f)
.OnComplete(() =>
{
// 퇴근 버튼 활성화
_quitButton.gameObject.SetActive(true);
// New Record 체크
if (resultData.Score > bestScore)
{
PlayerPrefs.SetFloat("BestScore", resultData.Score);
ShowNewRecordEffect();
}
}));
}
- InGameController.cs
<hide/>
//게임 종료. 결과 보고
public IEnumerator EndGame()
{
//TODO: 게임 끝낼 시 실행할 로직
//시간 정지
timeController.StopTime();
// 시간 부족 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;
}
//초기화.
_initComplete = false;
_skipResultUI = false;
// BGM 초기화
AudioManager.Instance.BGM.SetBGMVolumeMax(); // 볼륨을 최대로
//재시작 필요 시.
if (_useRetry)
{
//새 게임 코루틴 활성화
yield return GameManager.Instance.StartCoroutine(SetInitGame());
GameManager.Instance.StartCoroutine(RunSequence());
//재시작을 위해 타이틀로 복귀하지 않고 기존 코루틴을 중단한다.
yield break;
}
//인게임 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();
}
게임 난이도가 상승하면 BGM을 배속 재생하도록
: 게임 난이도가 상승하면 BGM이 더 빨리 재생되고, '퇴근하기' 버튼을 누르면 다시 BGM의 배속을 원상복귀
--> 게임 난이도에 따라 미리 설정해둔 값만큼 배속되도록 구현하고 Mathf.Clamp()를 활용하여 level 값의 최솟값, 최대값을 설정
▶ Action을 활용, 함수를 구독하여 난이도가 상승되면 BGM이 배속 재생되는 함수를 호출하도록 구현
: 다른 팀원 분께서 만들어 놓으신 'OnLevelChanged'를 활용
- DifficultyManager.cs (Singleton)
public static event Action OnLevelChanged; //난이도 상승때 동작하기 위한 이벤트. 구독하면 됩니다.
※ Unity 공식 문서 - Mathf.Clamp()
https://docs.unity3d.com/ScriptReference/Mathf.Clamp.html
>> 작성한 코드
- BGMController.cs
<hide/>
protected override void Initialize()
{
SceneManager.sceneLoaded += OnSceneLoaded;
_bgmSource = gameObject.AddComponent<AudioSource>();
DifficultyManager.OnLevelChanged += SetBGMSpeedFast; // 난이도가 상승하면 자동 실행되도록 구독
}
// BGM 배속 조절
private void SetBGMSpeedFast()
{
int level = DifficultyManager.Instance.GetLevel(GameManager.Instance.GetTimeController()._day);
float[] bgmSpeeds = { 1f, 1.1f, 1.2f, 1.3f, 1.5f };
int temp = Mathf.Clamp(level, 0, bgmSpeeds.Length - 1); // level이 5 이상 넘어가는 것을 방지하기 위한 임시값
_bgmSource.pitch = bgmSpeeds[temp];
}
// BGM 속도 정상화
public void SetBGMSpeedNormal()
{
if (_bgmSource != null)
{
_bgmSource.pitch = 1f;
}
}
- InGameController.cs
<hide/>
//게임 종료. 결과 보고
public IEnumerator EndGame()
{
//TODO: 게임 끝낼 시 실행할 로직
//시간 정지
timeController.StopTime();
// 시간 부족 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;
}
//초기화.
_initComplete = false;
_skipResultUI = false;
// BGM 초기화
AudioManager.Instance.BGM.SetBGMVolumeMax(); // 볼륨을 최대로
AudioManager.Instance.BGM.SetBGMSpeedNormal(); // 배속을 기본으로
//재시작 필요 시.
if (_useRetry)
{
//새 게임 코루틴 활성화
yield return GameManager.Instance.StartCoroutine(SetInitGame());
GameManager.Instance.StartCoroutine(RunSequence());
//재시작을 위해 타이틀로 복귀하지 않고 기존 코루틴을 중단한다.
yield break;
}
//인게임 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();
}
BGM 및 SFX 볼륨 조절
: BGM 및 SFX의 볼륨을 조절
--> SFX는 PlayOneShot()으로 재생되기 때문에 재생할 때 따로 Volume을 조절하여 재생할 수 있도록 코드 추가 (기본 값 1f)
※ Unity 공식 문서 - PlayOneShot()
https://docs.unity3d.com/ScriptReference/AudioSource.PlayOneShot.html
>> 작성한 코드
- BGMController.cs
<hide/>
// BGM 재생 (반복 O)
private void PlayBGM(AudioClip clip)
{
if (!_isBGMOn || clip == null) return;
_bgmSource.clip = clip;
_bgmSource.loop = true;
_bgmSource.volume = 0.6f;
_bgmSource.Play();
}
// BGM 볼륨을 절반으로 설정
public void SetBGMVolumeHalf()
{
if (_bgmSource != null)
{
_bgmSource.volume = 0.3f;
}
}
// BGM 볼륨을 기본으로 설정
public void SetBGMVolumeMax()
{
if (_bgmSource != null)
{
_bgmSource.volume = 0.6f;
}
}
- SFXController.cs
<hide/>
// 적용 예시
public void PlayDocSuccess() => PlaySFX(docSuccess, 0.6f);
public void PlayDocFail() => PlaySFX(docFail, 0.6f);
// SFX 1번 재생
private void PlaySFX(AudioClip clip, float volume = 1f)
{
if (!_isSFXOn || clip == null) return;
_sfxSource.PlayOneShot(clip, volume);
}
버그 수정
: LoopSFX 재생 중에 일시중지한 다음 Audio 토글로 소리를 껐다가 키면 LoopSFX가 재생이 안되는 버그
--> Audio 토글로 소리를 끌 때, BGMController와 마찬가지로 SFX도 Mute로 끄도록 수정
>> 수정 중에 생긴 버그 및 해결
: 이미 Audio 토글이 OFF인 상태에서 LoopSFX가 재생되는 상황이 된 다음 Audio 토글을 ON하면 LoopSFX가 재생되지 않는 버그 발생
--> PlayLoopSFX() 함수 안에 토글 bool값에 따른 처리를 삭제하여 해결
▶ 토글 값에 따른 처리를 삭제하니, Audio 토글이 OFF인 상태에서 LoopSFX가 재생되는 상황이 되면 LoopSFX가 재생되는 버그가 발생
--> PlayLoopSFX()를 통해 SFX를 처음 세팅할 때, Audio 토글이 OFF인지 확인하여 OFF 상태라면 Mute 하도록 추가
>> 작성한 코드
- SFXController.cs
<hide/>
// SFX 반복 재생
private void PlayLoopSFX(AudioClip clip)
{
if (clip == null) return;
if (_loopSources.ContainsKey(clip)) return; // 이미 재생 중이면 패스
var src = gameObject.AddComponent<AudioSource>();
src.playOnAwake = false;
src.loop = true;
src.clip = clip;
if (!_isSFXOn) src.mute = true;
src.Play();
_loopSources[clip] = src;
}
// _isSFXOn 조정
public void SetSFXOn(bool isSFXOn)
{
_isSFXOn = isSFXOn;
// 단발성 AudioSource
if (_sfxSource != null)
_sfxSource.mute = !_isSFXOn;
// 루프성 AudioSources
foreach (var key in _loopSources)
{
if (key.Value != null)
key.Value.mute = !_isSFXOn;
}
}
BGMController, SFXController 코드 리팩토링
: 현재 AudioManager가 Singleton으로 관리되고 있고 AudioManager를 통해 BGMController와 SFXController를 호출하여 사용할 수 있는데도 BGMController와 SFXController도 Singleton으로 관리되고 있다.
--> 따라서 불필요한 BGMController와 SFXController의 Singleton을 제거
- 각 Controller의 Singleton을 제거함에 따라 기존 코드를 AudioManager를 통해 접근하도록 수정
- AudioManager, BGMController를 Prefab화 하면서 AudioManager에 각 Controller 바인딩
>> 작성한 코드
- BGMController.cs
<hide/>
using UnityEngine;
using UnityEngine.SceneManagement;
using static Constants;
public class BGMController : MonoBehaviour
{
private void Awake()
{
if (AudioManager.Instance != null)
AudioManager.Instance.SetBGMController(this);
SceneManager.sceneLoaded += OnSceneLoaded;
DifficultyManager.OnLevelChanged += SetBGMSpeedFast; // 난이도가 상승하면 자동 실행되도록 구독
_bgmSource = gameObject.AddComponent<AudioSource>();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
// TODO: 유저 정보에 소리 설정이 OFF라면 재생되지 않도록
}
}
- SFXController.cs
<hide/>
public class SFXController : MonoBehaviour
{
private void Awake()
{
if (AudioManager.Instance != null)
AudioManager.Instance.SetSFXController(this);
SceneManager.sceneLoaded += OnSceneLoaded;
// AudioSource 초기화
_sfxSource = gameObject.AddComponent<AudioSource>();
_sfxSource.playOnAwake = false;
_loopSources = new Dictionary<AudioClip, AudioSource>();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
}
}
최종 코드
>> AudioManager.cs
<hide/>
using UnityEngine;
public class AudioManager : Singleton<AudioManager>
{
[SerializeField] private BGMController bgmController;
[SerializeField] private SFXController sfxController;
// 각 Controller가 필요하시면 아래와 같이 호출해주세요.
public BGMController BGM => bgmController;
public SFXController SFX => sfxController;
private bool _isAudioOn = true; // Audio 토글이 On인지, Off인지
protected override void Initialize()
{
base.Initialize();
_isAudioOn = true; // 기본값은 켜짐으로 설정
// 필요 시, 초기화 ex) 저장된 설정 로드
}
public void SetBGMController(BGMController bgmController)
{
this.bgmController = bgmController;
}
public void SetSFXController(SFXController sfxController)
{
this.sfxController = sfxController;
}
// Audio 토글을 통해 BGM 및 SFX On/Off
public void ToggleAudio()
{
_isAudioOn = !_isAudioOn;
bgmController.SetBGMOn(_isAudioOn);
sfxController.SetSFXOn(_isAudioOn);
}
public bool GetIsAudioOn()
{
return _isAudioOn;
}
}
>> BGMController.cs
<hide/>
using UnityEngine;
using UnityEngine.SceneManagement;
using static Constants;
public class BGMController : MonoBehaviour
{
// BGM을 추가하실 때, 여기에 추가해주세요.
[SerializeField] private AudioClip titleBGM;
[SerializeField] private AudioClip gameBGM;
// 여기까지
private AudioSource _bgmSource;
private bool _isBGMOn = true; // BGM이 켜져있는지 여부
public bool IsBGMOn() => _isBGMOn;
private void Awake()
{
if (AudioManager.Instance != null)
AudioManager.Instance.SetBGMController(this);
SceneManager.sceneLoaded += OnSceneLoaded;
DifficultyManager.OnLevelChanged += SetBGMSpeedFast; // 난이도가 상승하면 자동 실행되도록 구독
_bgmSource = gameObject.AddComponent<AudioSource>();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
// TODO: 유저 정보에 소리 설정이 OFF라면 재생되지 않도록
}
// BGM를 추가하신 뒤, 아래 함수 모음에 재생 함수를 작성해주세요. 그리고 작성하신 함수를 통해 사용하시면 됩니다.
#region PlayBGM 함수 모음
public void PlayTitleBGM() => PlayBGM(titleBGM);
public void PlayGameBGM() => PlayBGM(gameBGM);
#endregion
// Scene에 따라 BGM 재생
public void PlayBGMByState(GameState currentState)
{
switch (currentState)
{
case GameState.Title:
PlayTitleBGM();
break;
case GameState.InGame:
PlayGameBGM();
break;
default:
StopBGM();
break;
}
}
// BGM 재생 (반복 O)
private void PlayBGM(AudioClip clip)
{
if (!_isBGMOn || clip == null) return;
_bgmSource.clip = clip;
_bgmSource.loop = true;
_bgmSource.volume = 0.6f;
_bgmSource.Play();
}
// BGM 중지
private void StopBGM()
{
if (_bgmSource != null)
{
_bgmSource.Stop();
}
}
// BGM 음소거
private void MuteBGM()
{
if (_bgmSource != null)
{
_bgmSource.mute = true;
}
}
// BGM 음소거 해제
private void UnmuteBGM()
{
if (_bgmSource != null)
{
_bgmSource.mute = false;
}
}
// BGM 볼륨을 절반으로 설정
public void SetBGMVolumeHalf()
{
if (_bgmSource != null)
{
_bgmSource.volume = 0.3f;
}
}
// BGM 볼륨을 기본으로 설정
public void SetBGMVolumeMax()
{
if (_bgmSource != null)
{
_bgmSource.volume = 0.6f;
}
}
// BGM 배속 조절
private void SetBGMSpeedFast()
{
int level = DifficultyManager.Instance.GetLevel(GameManager.Instance.GetTimeController()._day);
float[] bgmSpeeds = { 1f, 1.1f, 1.2f, 1.3f, 1.5f };
int temp = Mathf.Clamp(level, 0, bgmSpeeds.Length - 1); // level이 5 이상 넘어가는 것을 방지하기 위한 임시값
_bgmSource.pitch = bgmSpeeds[temp];
}
// BGM 속도 정상화
public void SetBGMSpeedNormal()
{
if (_bgmSource != null)
{
_bgmSource.pitch = 1f;
}
}
// _isBGMOn값을 조정하고 그에 따라 BGM을 음소거 설정 및 해제
public void SetBGMOn(bool isBGMOn)
{
_isBGMOn = isBGMOn;
if (_isBGMOn) // 음소거 해제
{
UnmuteBGM();
}
else // 음소거
{
MuteBGM();
}
}
}
>> SFXController.cs
<hide/>
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SFXController : MonoBehaviour
{
// SFX를 추가하실 때 여기에 추가해주세요.
[Header("Common")]
[SerializeField] private AudioClip buttonClick;
[Header("InGame")]
[SerializeField] private AudioClip stamp;
[SerializeField] private AudioClip docSuccess;
[SerializeField] private AudioClip docFail;
[SerializeField] private AudioClip docSwap;
[SerializeField] private AudioClip obsBugPostHit;
[SerializeField] private AudioClip obsProcessTry;
[SerializeField] private AudioClip obsHandHit;
[SerializeField] private AudioClip obsFileEnvelopeOut;
[SerializeField] private AudioClip newRecordResult;
[SerializeField] private AudioClip newRecordScoreBar;
[SerializeField] private AudioClip speedUp;
[SerializeField] private AudioClip fever;
[SerializeField] private AudioClip timeOutAlert;
// 여기까지
private AudioSource _sfxSource; // 단발성 AudioSource
private Dictionary<AudioClip, AudioSource> _loopSources; // 반복용 AudioSource
private bool _isSFXOn = true; // SFX가 켜져있는지 여부
public bool GetIsSFXOn() => _isSFXOn;
private void Awake()
{
if (AudioManager.Instance != null)
AudioManager.Instance.SetSFXController(this);
SceneManager.sceneLoaded += OnSceneLoaded;
// AudioSource 초기화
_sfxSource = gameObject.AddComponent<AudioSource>();
_sfxSource.playOnAwake = false;
_loopSources = new Dictionary<AudioClip, AudioSource>();
}
private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
}
// SFX를 추가하신 뒤, 아래 함수 모음에 재생 함수를 작성해주세요. 그리고 작성하신 함수를 통해 사용하시면 됩니다.
// 1번 재생 : PlaySFX()
// 반복 재생 : PlayLoopSFX()
// 반복 재생 중지 : StopLoopSFX()
#region PlaySFX 함수 모음
public void PlayButtonClick() => PlaySFX(buttonClick);
public void PlayStamp() => PlaySFX(stamp);
public void PlayDocSuccess() => PlaySFX(docSuccess, 0.6f);
public void PlayDocFail() => PlaySFX(docFail, 0.6f);
public void PlayDocSwap() => PlaySFX(docSwap);
public void PlayObsBugPostHit() => PlaySFX(obsBugPostHit);
public void PlayObsProcessTry() => PlaySFX(obsProcessTry);
public void PlayObsHandHit() => PlaySFX(obsHandHit);
public void PlayObsFileEnvelopeOut() => PlaySFX(obsFileEnvelopeOut);
public void PlaySpeedUp() => PlaySFX(speedUp);
public void PlayFever() => PlaySFX(fever);
public void PlayTimeOutAlert() => PlayLoopSFX(timeOutAlert);
public void StopTimeOutAlert() => StopLoopSFX(timeOutAlert);
public void PlayNewRecordResult() => PlaySFX(newRecordResult);
public void PlayNewRecordScoreBar() => PlaySFX(newRecordScoreBar);
#endregion
// SFX 1번 재생
private void PlaySFX(AudioClip clip, float volume = 1f)
{
if (!_isSFXOn || clip == null) return;
_sfxSource.PlayOneShot(clip, volume);
}
// SFX 반복 재생
private void PlayLoopSFX(AudioClip clip)
{
if (clip == null) return;
if (_loopSources.ContainsKey(clip)) return; // 이미 재생 중이면 패스
var src = gameObject.AddComponent<AudioSource>();
src.playOnAwake = false;
src.loop = true;
src.clip = clip;
if (!_isSFXOn) src.mute = true;
src.Play();
_loopSources[clip] = src;
}
// SFX 반복 재생 중지
private void StopLoopSFX(AudioClip clip)
{
if (clip == null || !_loopSources.ContainsKey(clip)) return;
var src = _loopSources[clip];
if (src != null && src.isPlaying)
{
src.Stop();
Destroy(src);
}
_loopSources.Remove(clip);
}
// _isSFXOn 조정
public void SetSFXOn(bool isSFXOn)
{
_isSFXOn = isSFXOn;
// 단발성 AudioSource
if (_sfxSource != null)
_sfxSource.mute = !_isSFXOn;
// 루프성 AudioSources
foreach (var key in _loopSources)
{
if (key.Value != null)
key.Value.mute = !_isSFXOn;
}
}
}
>> 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 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);
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);
// 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);
Sequence seq = DOTween.Sequence();
// Day Count Up
seq.Append(DOTween.To(() => 0, x => dayText.text = x.ToString() + "일", resultData.Day, 1f));
seq.AppendInterval(0.2f);
// MaxCombo Count Up
seq.Append(DOTween.To(() => 0, x => maxComboText.text = x.ToString(), resultData.MaxCombo, 1f));
seq.AppendInterval(0.2f);
// Score Count Up
seq.Append(DOTween.To(() => 0, x => scoreText.text = x.ToString("N0"), resultData.Score, 1.5f)
.OnComplete(() =>
{
// 퇴근 버튼 활성화
_quitButton.gameObject.SetActive(true);
// New Record 체크
if (resultData.Score > bestScore)
{
PlayerPrefs.SetFloat("BestScore", resultData.Score);
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();
});
}
}
>> InGameController.cs
<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;
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;
//타이머 초기화
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();
// 시간 부족 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;
}
//초기화.
_initComplete = false;
_skipResultUI = false;
// BGM 초기화
AudioManager.Instance.BGM.SetBGMVolumeMax(); // 볼륨을 최대로
AudioManager.Instance.BGM.SetBGMSpeedNormal(); // 배속을 기본으로
//재시작 필요 시.
if (_useRetry)
{
//새 게임 코루틴 활성화
yield return GameManager.Instance.StartCoroutine(SetInitGame());
GameManager.Instance.StartCoroutine(RunSequence());
//재시작을 위해 타이틀로 복귀하지 않고 기존 코루틴을 중단한다.
yield break;
}
//인게임 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();
}
///게임 끝내기, 호출 시 진행중인 게임이 끝납니다.
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;
}
}
'Development > Internship' 카테고리의 다른 글
[멋사 로켓단 인턴쉽] 15일차 - NewRecord를 PlayerPrefs와 연동 (2) | 2025.08.27 |
---|---|
[멋사 로켓단 인턴쉽] 14일차 - 예비군으로 인해 불참 (0) | 2025.08.26 |
[멋사 로켓단 인턴쉽] 12일차 - SFXController 리팩토링 (2) | 2025.08.22 |
[멋사 로켓단 인턴쉽] 11일차 - 점수판 완성 및 버그 해결 (3) | 2025.08.21 |
[멋사 로켓단 인턴쉽] 10일차 - Result UI & Effect (0) | 2025.08.20 |