Quiz Master[3]

chory·2023년 2월 10일
0

Udemy

목록 보기
11/12

상세한 게임 진행 메커니즘은 아래와 같다.

  1. QuestionSO에서 문제 하나 출력(제한 시간 30초)
  2. 보기에서 선택지 하나 클릭(버튼 색상 변화)
    2-1. 정답 선택 시 정답 문구 출력
    2-2. 오답 선택 시 오답 문구 출력
    2-3. 선택할 때마다 점수 및 진행도 갱신
    2-4. 정답 표시 시간 10초 경과 후 다음 문제 출력
  3. 2번 과정 반복 중, 문제를 다 풀었다면 종료 화면으로 전환
    3-1. 리플레이 버튼 클릭 시 씬 리로드

핵심 메커니즘 구현 이전에, 곁가지가 되는 타이머와 점수 계산기를 먼저 만들어둔다.

// 타이머 스크립트
// Timer.cs

// 문제 풀이 시간, 정답 표시 시간
[SerializeField] float timeToCompleteQuestion = 30f;
[SerializeField] float timeToShowCorrectAnswer = 10f;

// 정답 표시 후 다음 문제로 넘어가는 체크를 위한 bool 변수
public bool loadNextQuestion;
// 제한 시간 변수
float timerValue;

// 문제 풀이 중인지 정답 확인 중인지 구분을 위한 bool 변수
public bool isAnsweringQuestion = false;
// 타이머 이미지 조절용 변수
public float fillFraction;

void Update()
{
    UpdateTimer();
}

// 정답 선택 등의 경우에 타이머 종료를 위한 메소드
public void CancelTimer()
{
    timerValue = 0;
}

// 타이머 기능 메소드
void UpdateTimer()
{
    timerValue -= Time.deltaTime;
    
    // 문제 풀이 중인 경우
    if (isAnsweringQuestion)
    {
        if (timerValue > 0)
        {	
        	// 타이머 = 문제 풀이 시간
            fillFraction = timerValue / timeToCompleteQuestion;
        }
        // 문제 풀이 시간 종료 시
        else
        {
        	// 문제 풀이중인지 체크용 bool값 변경
            isAnsweringQuestion = false;
            // 정답 표시 시간으로 타이머 변경
            timerValue = timeToShowCorrectAnswer;
        }
    }
    
    // 정답 표시 중인 경우
    else
    {
        if (timerValue > 0)
        {
        	// 타이머 = 정답 표시 시간
            fillFraction = timerValue / timeToShowCorrectAnswer;
        }
        // 정답 표시 시간 종료  시
        else
            {
            // 문제 풀이 체크 bool 값 변경
            isAnsweringQuestion = true;
           	// 문제 풀이 시간으로 타이머 변경
            timerValue = timeToCompleteQuestion;
            // 다음 문제 호출용 bool값 변경
            loadNextQuestion =  true;
        }
    }
}
// 점수계산기 스크립트
// ScoreKeeper.cs

// 맞힌 정답 수
int correctAnswers = 0;
// 이때까지 푼 문제 수
int questionsSeen = 0;

// 맞힌 정답 수 반환 메소드
public int GetCorrectAnswerIndex()
{
    return correctAnswers;
}

// 맞힌 정답 수 증가 메소드
public void IncrementCorrectAnswers()
{
    correctAnswers++;
}

// 이때가지 푼 문제수 반환 메소드
public int GetQuestionsSeen()
{
    return questionsSeen;
}

// 이때까지 푼 문제수 증가 메소드
public void IncrementQuestionsSeen()
{
    questionsSeen++;
}

// 현재 점수 계산 메소드
public int CalculateScore()
{
	// (맞힌 문제 수/이때까지 푼 문제 수) 백분율 반환
    return Mathf.RoundToInt(correctAnswers / (float)questionsSeen * 100);
}

이렇게 핵심 메커니즘 구현에 필요한 타이머, 점수 계산기 스크립트를 모두 작성했다. 이제 Quiz 스크립트를 작성할 차례다.

코드가 비교적 긴 편이라, 각 메소드별로 나누어서 작성한다.

1. 정답 체크 메소드

void DisplayAnswer(int index)
{
	// 버튼 이미지 선언
    Image buttonImage;
	
    // 선택한 버튼이 정답인 경우 처리
    if (index == currentQuestion.GetCorrectAnswerIndex())
    {
        questionText.text = "Correct!";
        // 선택한 버튼 이미지 변경
        buttonImage = answerButtons[index].GetComponent<Image>();
        buttonImage.sprite = correctAnswerSprite;
 		// 정답 맞힌 수 증가       
        scoreKeeper.IncrementCorrectAnswers();
    }
    // 선택한 버튼이 오답인 경우 처리
    else
    {
    	// 정답 인덱스
        correctAnswerIndex = currentQuestion.GetCorrectAnswerIndex();
        // 정답 스트링
        string correctAnswer = currentQuestion.GetAnswer(correctAnswerIndex);
        // 정답 안내 문구 출력
        questionText.text = "Wrong, the correct answer was;\n" + correctAnswer;
	
    // 정답 선택지의 버튼 이미지 변경
    buttonImage = answerButtons[correctAnswerIndex].GetComponent<Image>();
    buttonImage.sprite = correctAnswerSprite;
    }
}

2. 버튼 클릭 시 호출할 메소드

public void OnAnswerSelected(int index)
{
	// 시간초과 전에 선택했으므로 bool값 수정
    hasAnsweredEarly = true;
    // 정답 체크
    DisplayAnswer(index);
    // 버튼 비활성화
    SetButtonState(false);
    // 타이머 스크립트의 초기화 메소드 호출
    timer.CancelTimer();
    
    // 점수 갱신
    scoreText.text = "Score:" + scoreKeeper.CalculateScore() + " %";
}

3. 문제를 랜덤으로 받아오는 메소드

void GetRandomQuestion()
{
	
    int index = Random.Range(0, questions.Count);
    currentQuestion = questions[index];

	// 받아온 랜덤 문제 남은 문제 목록에서 삭제
    if (questions.Contains(currentQuestion))
    {
    	questions.Remove(currentQuestion);
    }
}

4. 버튼 활성화/비활성화 메소드

void SetButtonState(bool state)
{
    for (int i = 0; i < answerButtons.Length; i++)
    {
        Button button = answerButtons[i].GetComponent<Button>();
        button.interactable = state;
    }
}

5. 선택지 표시 메소드

void DisplayQuestion()
{
    questionText.text = currentQuestion.GetQuestion();
	
    // 각 버튼에 인덱스별 선택지 할당
    for (int i = 0; i < answerButtons.Length; i++)
    {
        TextMeshProUGUI buttonText = answerButtons[i].GetComponentInChildren<TextMeshProUGUI>();
        buttonText.text = currentQuestion.GetAnswer(i);
    }
}

6. 버튼 이미지 초기화 메소드

void SetDefaultButtonSprites()
{
    Image buttonImage;

    for (int i = 0; i < answerButtons.Length; i++)
    {
        buttonImage = answerButtons[i].GetComponent<Image>();
        buttonImage.sprite = defalutAnswerSprite;
    }
}

7. 다음 문제를 불러오는 메소드

void GetNextQuestion()
{
    if (questions.Count > 0)
    {
    	// 버튼 활성화
        SetButtonState(true);
        // 버튼 이미지 초기화
        SetDefaultButtonSprites();
        // 랜덤으로 문제 받아오기
        GetRandomQuestion();
        // 선택지 표시
        DisplayQuestion();
        // 진행도 증가
        progressBar.value++;
        // 이때까지 푼 문제수 증가
        scoreKeeper.IncrementQuestionsSeen();
    }
}

이제, 위 메소드들을 활용하여 Quiz 스크립트를 작성한다.

// 직렬화 값들을 한눈에 볼 수 있도록 Header로 정리
[Header("Questions")]
[SerializeField] TextMeshProUGUI questionText;
[SerializeField] List<QuestionSO> questions = new List<QuestionSO>();
QuestionSO currentQuestion;

[Header("Answers")]
[SerializeField] GameObject[] answerButtons;
int correctAnswerIndex;
// 시간 초과 전에 정답을 고른 경우 체크용 bool
bool hasAnsweredEarly = true;

[Header("Button Colors")]
[SerializeField] Sprite defalutAnswerSprite;
[SerializeField] Sprite correctAnswerSprite;

[Header("Timer")]
[SerializeField] Image timerImage;
Timer timer;

[Header("Scoring")]
[SerializeField] TextMeshProUGUI scoreText;
ScoreKeeper scoreKeeper;

[Header("ProgressBar")]
[SerializeField] Slider progressBar;

// 게임 종료 여부 체크용 bool
public bool isComplete;

void Awake()
{
	// 변수 초기화 작업
    timer = FindObjectOfType<Timer>();
    scoreKeeper = FindObjectOfType<ScoreKeeper>();
    progressBar.maxValue = questions.Count;
    progressBar.value = 0;
}

void Update()
{
	// 현재 남은 제한시간에 맞추어 타이머 표시
    timerImage.fillAmount = timer.fillFraction;
    // 정답 확인 시간 만료 시
    if (timer.loadNextQuestion)
    {       
    	// 진행도가 가득 찬 경우
        if (progressBar.value == progressBar.maxValue)
        {
        	// 완료 bool 값 변경
            isComplete = true;
            return;
        }
        
		// 정답 선택 확인 bool 값 변경
        hasAnsweredEarly = false;
        // 다음 문제로
        GetNextQuestion();
        // 다음 문제 호출 체크용 bool 값 변경
        timer.loadNextQuestion = false;
    }
    // 진행도가 가득 차지 않은 경우
    // 타이머 만료까지 정답을 고르지 않은 경우
    else if (!hasAnsweredEarly && !timer.isAnsweringQuestion)
    {
        DisplayAnswer(-1);
        SetButtonState(false);
    }
}

이렇게 게임의 핵심 메커니즘 구현을 마쳤다.

profile
게임 만들고 싶어요

0개의 댓글