목차
멋쟁이사자처럼 로켓단 인턴쉽
25.08.21
회의록
>> 안건
- 업무 업데이트
- 질문 혹은 피드백 요청
- 주요 안건
- 게임 완전완성 지속중
- 난이도 밸런싱 적용
- 향후 마일스톤
- 디테일, 버그 등 지속작업
- itch.io 업로드
>> 회의 내용
- 금요일까지 완성하기.
점수판 완성
>> 점수판 Image 수정
: 점수판 Image가 다른 팀원 분이 디자인 해주신대로 되어있지 않아서 완전히 적용
>> New Record시, 점수판 위에 Image 및 Animation 추가
: New Record시, 점수판에 들어가있던 'New Record' 이미지가 위로 튀어오르도록 제작
--> 'New Record' 이미지의 초기 위치를 점수판 안에 있도록 위치한 뒤, DOTween의 'DOAnchorPosY()' 를 사용하여 위치를 올려주는 Animation과 함께 'SetEase(Ease.OutBack)'을 사용하여 튀어오르는 느낌이 나도록 구현
- 재시도를 하거나 게임을 계속 이어서 할 경우에 위치를 초기화하는 함수도 같이 제작
- 점수판의 위치는 Unity에서 위치를 보고 값을 정했기 때문에 readonly로 설정
- 현재 리더보드를 불러오는 기능이 없기 때문에 임시 코드로 'K'를 입력하면 효과가 나타나도록 제작
※ Readonly vs Const
C#의 readonly 키워드와 불변성에 대해
readonly vs const 처음 readonly를 접하면, 자연스럽게 const와 비교하게 된다. 언뜻 보기에는 둘다 변하지 않는 값을 선언하는 문법으로, 큰 차이를 느낄 수 없다. 다른 점을 찾아봐도 주로 const는 컴파
tearsinrain.tistory.com
- 적용한 모습
└ 최종 코드
1. ScoreUIController.cs
<hide/>
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
public class ScoreUIController : MonoBehaviour
{
public TMP_Text score;
public TMP_Text scoreMag;
[SerializeField] private Image newRecordImage;
private readonly float _hiddenY = 23f; // Unity에서 위치를 보고 맞춘 값
private readonly float _shownY = 58f; // Unity에서 위치를 보고 맞춘 값
// New Record 이미지 위치 초기화
public void InitNewRecordImage()
{
Vector2 newRecordPos = newRecordImage.rectTransform.anchoredPosition;
newRecordPos.y = _hiddenY;
newRecordImage.rectTransform.anchoredPosition = newRecordPos;
}
// New Record Animation
public void ShowNewRecordImage()
{
newRecordImage.rectTransform.DOAnchorPosY(_shownY, 0.3f).SetEase(Ease.OutBack);
}
}
2. InGameController.cs
<hide/>
//게임 시작 전 초기화
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();
}
3. InGameState.cs
<hide/>
public void OnUpdate()
{
// TODO: 유저의 최고기록 불러오기 (임시: 'K'를 누르면 연출 재생)
// New Record시, 점수판에 New Record Image 연출 재생
if (Input.GetKeyDown(KeyCode.K))
{
InGameUIController.Instance.scoreUIController.ShowNewRecordImage();
}
}
버그 발견 및 수정
>> 발견한 버그
- 버튼을 연속으로 눌렀을 때 서류 분류 효과(?)의 잔상이 남아있음
- 서류를 잘못 분류했을 때, '시간 감소' 글자가 사라진 뒤에야 일과시간이 줄어든다.
- 서류를 올바르게 분류했을 때, 시간이 증가한다고 나오지만 숫자가 안 오를 때가 있음 --> 위와 마찬가지로 글자가 사라진 뒤에야 일과시간이 늘어나서 그런게 아닐까?
- 장애물을 제거하지 않고 분류했을 때 일과시간이 더 많이 깎인다. --> Day1에는 6~7초 정도, Day3에는 10~11초 정도 줄어들었다. (원래 장애물을 제거하지 않았을 때 더 많이 깎였는가? + 그렇다해도 단계별이 아니라 Day 수에 비례해서 더 많이 깎이는 버그가 있다.)
- 게임 1판을 끝내고 다시 게임을 시작했을 때나 일시정지 후 재시작을 했을 때, 시계의 프레임 색이 이전 상태를 유지한 채로 시작된다. 그리고 3초 후 본 게임이 시작될 때 다시 초기화 된다.
- 게임 1판을 끝내고 다시 게임을 한 다음, 다시 결과창에 진입하면 New Record Image가 붙어있는 상태로 나온다.
>> 버그 수정
: 여러 버그들을 팀원들끼리 나눠서 수정했고, 나는 결과창의 New Record Image 관련 버그와 시계 프레임 색 관련 버그를 수정했다.
1. New Record Image 버그
: 기존 코드에서는 ResultUIController.cs가 Awake()할 때 newRecordImage를 SetActive(false)해서 재시작했을 때 Awake()는 호출되지 않으므로 발생한 문제였다.
--> Awake()가 아니라 InitResultItem()에서 SetActive(false)를 호출하도록 수정
- 수정된 모습
- 수정한 코드
<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;
void Awake()
{
_quitButton.onClick.AddListener(OnClickQuitButton);
}
public void ShowPopup()
{
base.ShowPopup(gameObject);
}
public void ClosePopup()
{
base.ClosePopup(gameObject);
}
public void InitResultItem(GameResultData resultData)
{
// FadeOut Panel 초기화
fadeOutCanvasGroup.alpha = 0;
// 퇴근 버튼 비활성화
_quitButton.gameObject.SetActive(false);
// New Record 이미지 비활성화
newRecordImage.gameObject.SetActive(false);
// 처음에는 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));
// 살짝 튕기면서 원래 크기로
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();
});
}
}
2. 시계 프레임 색 버그
: 기존 코드에서는 남은 일과시간이 30초 이하일 때, 시계 프레임이 흰색과 빨간색을 번갈아가며 변하는 코드만 있었고, 재시작 할 때 초기화 해주는 코드가 없었다.
--> 시계 프레임 색상을 초기화 해주는 함수를 작성한 후, 게임이 시작할 때 호출하도록 수정
▶ 기존 코드에서는 clockHandle과 clockFrame을 Awake()에서 GetChild()로 긁어와서 사용했는데, 그러다보니 InGameController.cs에서 초기화할 때 clockFrame을 찾지 못해 Null에러가 발생했다.
--> Awake()를 없애고 clockHandle과 clockFrame을 모두 SerializeField로 받아서 작동하도록 수정
- 수정된 모습
- 수정한 코드
>> ClockUIController.cs
<hide/>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ClockUIController : MonoBehaviour
{
[SerializeField] GameObject clockHandle;
[SerializeField] Image clockFrame;
[Header("각도 변화량")]
public float stepAngle = 30f;
[Header("틱 간격")]
public float tickInterval = 1.0f;
[Header("경보 시간 설정")]
public int setTime = 30;
float targetZ;
float timer;
void Update()
{
if (!TimeController.Instance._isTimeRunning) return;
ClockHandAnimation();
ClockFrameColor();
}
public void ClockHandAnimation() // 시계 바늘 움직이는 기능
{
timer += Time.deltaTime;
if (timer >= tickInterval) // 틱 간격이 지났을 때
{
// 목표 각도를 현재 각도에서 stepAngle 만큼 증가
targetZ += -stepAngle; // 시계방향으로 회전하기위해 -를 붙임
timer = 0f;
}
// 목표 각도로 보간
clockHandle.transform.rotation = Quaternion.Lerp(clockHandle.transform.rotation, Quaternion.Euler(0, 0, targetZ), Time.deltaTime * 10f);
}
public void ClockFrameColor() // 시계 프레임 색상 변경 기능
{
float remainedTime = TimeController.Instance._remainedTimerTime;
if (remainedTime <= setTime) // 일과 시간이 30초 이하일 때
{
float t = Mathf.PingPong(Time.time * 2f, 1f); // 색상 변경 딜레이
clockFrame.color = Color.Lerp(Color.white, Color.red, t); // 흰색에서 빨간색으로 보간
/* SFX, VFX 등 추가할거 있으면 여기에 추가 및 수정하시면 됩니다 */
}
else
{
InitClockFrameColor(); // 기본 색상으로 설정
}
}
public void InitClockFrameColor() // 시계 프레임 색상 초기화
{
clockFrame.color = Color.white;
}
}
>> InGameController.cs
<hide/>
//게임 시작 전 초기화
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();
}
게임에 쓰일 BGM 및 SFX를 구하기 전 고민
: 게임에 필요한 BGM 및 SFX가 무엇이 있을지 혼자 고민해보았다.
- 게임에 필요한 BGM 및 SFX 목록 정리
- BGM
- Title BGM
- InGame BGM
- SFX
- Click SFX
- UI Close SFX
- InGame Count SFX
- (InGame Start SFX)
- Stamp SFX
- (InGame NewRecord SFX)
- Obstacle SFX - 종류별로
- Result NewRecord SFX
'Development > Internship' 카테고리의 다른 글
[멋사 로켓단 인턴쉽] 13일차 - Audio 관련 기능 추가 및 버그 수정 (1) | 2025.08.25 |
---|---|
[멋사 로켓단 인턴쉽] 12일차 - SFXController 리팩토링 (2) | 2025.08.22 |
[멋사 로켓단 인턴쉽] 10일차 - Result UI & Effect (0) | 2025.08.20 |
[멋사 로켓단 인턴쉽] 9일차 - Result Effect 및 BGM Bug Fix (0) | 2025.08.19 |
[멋사 로켓단 인턴쉽] 8일차 - Stamp Effect 및 Result Effect (6) | 2025.08.18 |