TIL
[멋쟁이사자처럼 부트캠프 TIL 회고] 유니티 게임개발 3기 55일차 퀴즈게임 제작4
HYttt
2025. 2. 18. 01:10
퀴즈게임 제작
퀴즈카드에 타이머 추가
퀴즈가 시작되면 타이머 작동
뒤에 있는 카드는 타이머 작동하지 않도록
- SetVisible로 첫번쨰 카드만 타이머가 작동하도록 구현
- 보기를 클릭하면 타이머 일시정지
public struct QuizData
{
public string question;
public string description;
public int type;
public int answer;
public string firstOption;
public string secondOption;
public string thirdOption;
}
public class QuizCardController : MonoBehaviour
{
// Timer
[SerializeField] private Timer timer;
public void SetVisible(bool isVisible)
{
if (isVisible)
{
timer.InitTimer();
timer.StartTImer();
}
else
{
timer.InitTimer();
}
}
public void OnClickOptionButton(int buttonIndex)
{
timer.PauseTimer();
if (buttonIndex == _answer)
{
Debug.Log("정답");
// Todo : 정답 연출
SetQuizPanelActive(QuizCardPanelType.CorrectBack);
}
else
{
Debug.Log("오답");
// Todo : 오답 연출
SetQuizPanelActive(QuizCardPanelType.IncorrectBack);
}
}
}
// GamePanelController
private void SetQuizCardPosition(GameObject quizCardObject, int index)
{
var quizCardTransform = quizCardObject.GetComponent<RectTransform>();
if (index == 0)
{
quizCardTransform.DOAnchorPos(new Vector2(0,0), 0.5f);
quizCardTransform.DOScale(Vector3.one, 0.5f);
quizCardTransform.SetAsLastSibling();
quizCardObject.GetComponent<QuizCardController>().SetVisible(true);
}
if (index == 1)
{
quizCardTransform.DOAnchorPos(new Vector2(0,160), 0.5f);
quizCardTransform.DOScale(Vector3.one * 0.9f, 0.5f);
quizCardTransform.SetAsFirstSibling();
quizCardObject.GetComponent<QuizCardController>().SetVisible(false);
}
}
타이머 종료시 오답처리
- 델리게이트로 타이머가 종료되면 알림
- QuizCardController에서 타이머가 종료되면 오답패널을 띄우도록 구독
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;
public class Timer : MonoBehaviour
{
[SerializeField] private Image fillImage;
[SerializeField] private float totalTime;
[SerializeField] private Image headCapImage;
[SerializeField] private Image tailCapImage;
[SerializeField] private TMP_Text timeText;
public float CurrentTime { get; private set; }
private bool _isPaused;
public delegate void TimerDelegate();
public TimerDelegate OnTimeout;
private void Awake()
{
_isPaused = true;
}
private void Update()
{
if(!_isPaused)
{
CurrentTime += Time.deltaTime;
if(CurrentTime >= totalTime)
{
headCapImage.gameObject.SetActive(false);
tailCapImage.gameObject.SetActive(false);
_isPaused = true;
OnTimeout?.Invoke();
}
else
{
fillImage.fillAmount = 1 - CurrentTime / totalTime;
headCapImage.transform.localRotation = Quaternion.Euler(new Vector3(0,0,-fillImage.fillAmount * 360));
var timeTextTime = totalTime - CurrentTime;
timeText.text = timeTextTime.ToString("F0");
}
}
}
public void StartTImer()
{
_isPaused = false;
}
public void PauseTimer()
{
_isPaused = true;
}
public void InitTimer()
{
CurrentTime = 0;
fillImage.fillAmount = 1;
timeText.text = totalTime.ToString("F0");
headCapImage.gameObject.SetActive(true);
tailCapImage.gameObject.SetActive(true);
_isPaused = true;
}
}
public struct QuizData
{
public string question;
public string description;
public int type;
public int answer;
public string firstOption;
public string secondOption;
public string thirdOption;
}
public class QuizCardController : MonoBehaviour
{
// Timer
[SerializeField] private Timer timer;
private void Start()
{
timer.OnTimeout += () =>
{
SetQuizPanelActive(QuizCardPanelType.IncorrectBack);
};
}
public void SetVisible(bool isVisible)
{
if (isVisible)
{
timer.InitTimer();
timer.StartTImer();
}
else
{
timer.InitTimer();
}
}
}
퀴즈카드에 하트패널 추가
Remove, Add, Empty 애니메이션, 사운드 추가
using System;
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(AudioSource))]
public class HeartPanelController : MonoBehaviour
{
[SerializeField] private GameObject heartRemoveImageObject;
[SerializeField] private TMP_Text heartCountText;
[SerializeField] private AudioClip heartRemoveAudioClip;
[SerializeField] private AudioClip heartAddAudioClip;
[SerializeField] private AudioClip heartEmptyAudioClip;
private AudioSource _audioSource;
private int _heartCount;
// 1. 하트 추가 연출
// 2. 하트 감소 연출
// 3. 하트 부족 연출
private void Awake()
{
_audioSource = GetComponent<AudioSource>();
}
private void Start()
{
heartRemoveImageObject.gameObject.SetActive(false);
//InitHeartCount(10);
InitHeartCount(UserInformations.HeartCount);
}
/// <summary>
/// Heart Panel에 하트 수 초기화
/// </summary>
/// <param name="heartCount">하트 수</param>
public void InitHeartCount(int heartCount)
{
_heartCount = heartCount;
heartCountText.text = _heartCount.ToString();
}
private void ChangeTextAnimation(bool isAdd)
{
float duration = 0.2f;
float yPos = 40f;
heartCountText.rectTransform.DOAnchorPosY(-yPos, duration);
heartCountText.DOFade(0, duration).OnComplete(() =>
{
if (isAdd)
{
var currentHeartCount = heartCountText.text;
heartCountText.text = (int.Parse(currentHeartCount) + 1).ToString();
}
else
{
var currentHeartCount = heartCountText.text;
heartCountText.text = (int.Parse(currentHeartCount) - 1).ToString();
}
var textLength = heartCountText.text.Length;
GetComponent<RectTransform>().sizeDelta = new Vector2(100 + textLength * 30f, 100f);
heartCountText.rectTransform.DOAnchorPosY(yPos, 0);
heartCountText.rectTransform.DOAnchorPosY(0f, duration);
heartCountText.DOFade(1, duration).OnComplete(() =>
{
});
});
}
public void AddHeart(int heartCount)
{
Sequence sequence = DOTween.Sequence();
for (int i = 0; i < 3; i++)
{
sequence.AppendCallback(() =>
{
ChangeTextAnimation(true);
if (UserInformations.IsPlaySFX)
{
_audioSource.PlayOneShot(heartAddAudioClip);
}
});
sequence.AppendInterval(0.3f);
}
}
public void EmptyHeart()
{
if (UserInformations.IsPlaySFX)
{
_audioSource.PlayOneShot(heartEmptyAudioClip);
}
GetComponent<RectTransform>().DOPunchPosition(new Vector3(20f, 0, 0), 1f, 7);
}
public void RemoveHeart()
{
// 하트 사라지는 연출
if (UserInformations.IsPlaySFX)
{
_audioSource.PlayOneShot(heartRemoveAudioClip);
}
heartRemoveImageObject.gameObject.SetActive(true);
heartRemoveImageObject.transform.localScale = Vector3.zero;
heartRemoveImageObject.GetComponent<Image>().color = Color.white;
heartRemoveImageObject.transform.DOScale(3f, 1f);
heartRemoveImageObject.GetComponent<Image>().DOFade(0f, 1f);
ChangeTextAnimation(false);
}
}
Incorrect Panel에 남은 하트 수 표시
- 다시 도전 버튼을 클릭하면 하트 차감하고 Front Panel을 다시 보여줌
- 애니메이션이 종료되면 화면이 전환되도록 델리게이트로 전달
[RequireComponent(typeof(AudioSource))]
public class HeartPanelController : MonoBehaviour
{
public delegate void HeartPanelDelegate();
public HeartPanelDelegate AnimationDone;
private void ChangeTextAnimation(bool isAdd)
{
float duration = 0.2f;
float yPos = 40f;
heartCountText.rectTransform.DOAnchorPosY(-yPos, duration);
heartCountText.DOFade(0, duration).OnComplete(() =>
{
if (isAdd)
{
var currentHeartCount = heartCountText.text;
heartCountText.text = (int.Parse(currentHeartCount) + 1).ToString();
}
else
{
var currentHeartCount = heartCountText.text;
heartCountText.text = (int.Parse(currentHeartCount) - 1).ToString();
}
var textLength = heartCountText.text.Length;
GetComponent<RectTransform>().sizeDelta = new Vector2(100 + textLength * 30f, 100f);
heartCountText.rectTransform.DOAnchorPosY(yPos, 0);
heartCountText.rectTransform.DOAnchorPosY(0f, duration);
heartCountText.DOFade(1, duration).OnComplete(() =>
{
AnimationDone?.Invoke();
});
});
}
}