using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; using System.Collections.Generic; using System.Collections; public class Pronunciation13Mod : MonoBehaviour { [Header("TXT FILE")] public TextAsset pronunciationFile; [Header("UI")] public TMP_Text questionText; public TMP_Text languageText; public Button[] answerButtons; public TMP_Text[] answerTexts; public TMP_Text feedbackText; public Button submitButton; public TMP_Text scoreText; public TMP_Text comboText; public TMP_Text timerText; [Header("QUESTION FONT")] public bool questionUppercase = true; public float questionFontSize = 86f; public Color questionColor = Color.white; public FontStyles questionFontStyle = FontStyles.Bold; public TextAlignmentOptions questionAlignment = TextAlignmentOptions.Center; public float questionCharacterSpacing = 4f; [Header("EXTRA BUTTONS")] public Button extraTimeButton; public Button extraHintButton; public Button extraSkipButton; public TMP_Text extraTimeCooldownText; public TMP_Text extraHintCooldownText; public TMP_Text extraSkipCooldownText; [Header("EXTRA SETTINGS")] public float extraTimeAmount = 5f; public float cooldownDuration = 5f; [Header("SCENES")] public Button backButton; public string backSceneName = "modsec 13"; public string resultSceneName = "modres 13"; [Header("LANGUAGE")] public string forcedLanguageCode = ""; [Header("GAME SETTINGS")] public float startTime = 60f; public int basePoints = 50; public int wrongPenalty = 50; public float correctTimeBonus = 2f; public float nextQuestionDelay = 1.15f; [Header("COMBO")] public float mult_1to2 = 1f; public float mult_3to4 = 1.15f; public float mult_5to7 = 1.3f; public float mult_8plus = 1.5f; [Header("COLORS")] public Color normalButtonColor = new Color32(120, 70, 220, 255); public Color selectedButtonColor = new Color32(155, 105, 255, 255); public Color correctButtonColor = new Color32(70, 240, 130, 255); public Color wrongButtonColor = new Color32(255, 80, 95, 255); public Color disabledButtonColor = new Color32(90, 75, 120, 255); [Header("ANIMATION")] [Range(0.80f, 1f)] public float selectedScale = 0.94f; public float selectAnimTime = 0.12f; public float enterAnimTime = 0.30f; public float enterStagger = 0.07f; const string modKey = "Mod13"; readonly string[] langCodes = { "tr", "en", "es", "de", "fr", "pt" }; readonly string[] langNames = { "TR", "EN", "ES", "DE", "FR", "PT" }; class PronunciationEntry { public string[] words = new string[6]; // Unity string[,] serialize edemez. // 6 dil x 4 seçenek = 24 alan olarak tutulur. public string[] options = new string[24]; public void SetOption(int langIndex, int optionIndex, string value) { options[langIndex * 4 + optionIndex] = value; } public string GetOption(int langIndex, int optionIndex) { return options[langIndex * 4 + optionIndex]; } } readonly List questionPool = new List(); int selectedLangIndex = 1; int questionIndex = 0; int correctAnswerIndex = -1; int selectedAnswer = -1; int lastQuestionShown = -1; int score = 0; int combo = 0; int correctCount = 0; int wrongCount = 0; int bestComboThisRun = 0; float timeLeft; bool gameOver = false; bool waitingNextQuestion = false; bool extraTimeReady = true; bool extraHintReady = true; bool extraSkipReady = true; Vector3[] originalButtonScales; Vector3[] originalButtonPositions; Coroutine[] buttonScaleCoroutines; Coroutine[] buttonEnterCoroutines; readonly Color timerNormalColor = Color.white; readonly Color timerWarnColor = new Color32(255, 80, 80, 255); void Awake() { LoadQuestions(); } void Start() { selectedLangIndex = ResolveLanguageIndex(); timeLeft = PlayerPrefs.GetFloat("SelectedGameTime", startTime); if (timeLeft <= 0) timeLeft = startTime; if (questionPool.Count < 1) { Debug.LogError("Mod 13 TXT boş veya hatalı!"); enabled = false; return; } if (answerButtons == null || answerButtons.Length < 4 || answerTexts == null || answerTexts.Length < 4) { Debug.LogError("Mod13 için 4 answerButtons ve 4 answerTexts gerekli!"); enabled = false; return; } HideCooldownTexts(); CacheButtonData(); ForceButtonTransitionNone(); PrepareQuestionFont(); PrepareFeedbackText(); BindMainButtons(); Shuffle(questionPool); UpdateScore(); UpdateCombo(); UpdateTimerUI(); UpdateLanguageUI(); NextQuestion(); } void Update() { if (gameOver) return; timeLeft -= Time.deltaTime; if (timeLeft <= 0) { timeLeft = 0; UpdateTimerUI(); GameOver(); return; } UpdateTimerUI(); } void LoadQuestions() { questionPool.Clear(); if (pronunciationFile == null) { Debug.LogError("Pronunciation13 TXT atanmadı!"); return; } string[] lines = pronunciationFile.text.Split( new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries ); for (int lineIndex = 0; lineIndex < lines.Length; lineIndex++) { string line = lines[lineIndex].Trim(); if (string.IsNullOrWhiteSpace(line)) continue; string lower = line.ToLower(); if (lower.StartsWith("tr,en,es,de,fr,pt")) continue; if (lower.StartsWith("tr|en|es|de|fr|pt")) continue; string[] p = SplitLineSmart(line); if (p.Length < 30) { Debug.LogWarning("Mod13 eksik satır atlandı. Satır: " + (lineIndex + 1) + " Alan: " + p.Length); continue; } PronunciationEntry e = new PronunciationEntry(); bool valid = true; for (int i = 0; i < 6; i++) { e.words[i] = CleanField(p[i]); if (string.IsNullOrWhiteSpace(e.words[i])) valid = false; } int cursor = 6; for (int lang = 0; lang < 6; lang++) { for (int opt = 0; opt < 4; opt++) { string value = CleanField(p[cursor]); e.SetOption(lang, opt, value); if (string.IsNullOrWhiteSpace(value)) valid = false; cursor++; } } if (valid) questionPool.Add(e); else Debug.LogWarning("Mod13 boş alanlı satır atlandı. Satır: " + (lineIndex + 1)); } } string[] SplitLineSmart(string line) { if (line.Contains("|")) return line.Split('|'); if (line.Contains("\t")) return line.Split('\t'); return SplitCsvLine(line).ToArray(); } List SplitCsvLine(string line) { List result = new List(); bool inQuotes = false; string current = ""; for (int i = 0; i < line.Length; i++) { char ch = line[i]; if (ch == '"') { inQuotes = !inQuotes; continue; } if (ch == ',' && !inQuotes) { result.Add(current); current = ""; } else { current += ch; } } result.Add(current); return result; } string CleanField(string s) { if (string.IsNullOrEmpty(s)) return ""; return s.Trim().Trim('"').Trim(); } int ResolveLanguageIndex() { string code = forcedLanguageCode.Trim().ToLower(); if (string.IsNullOrEmpty(code)) code = PlayerPrefs.GetString("PronunciationLangCode", "").Trim().ToLower(); if (string.IsNullOrEmpty(code)) code = PlayerPrefs.GetString("QuestionLangCode", "").Trim().ToLower(); if (!string.IsNullOrEmpty(code)) { for (int i = 0; i < langCodes.Length; i++) { if (code == langCodes[i]) return i; } } int appLang = PlayerPrefs.GetInt("AppLanguage", 1); if (appLang < 0 || appLang > 5) appLang = 1; return appLang; } void PrepareQuestionFont() { if (questionText == null) return; questionText.fontSize = questionFontSize; questionText.color = FullAlpha(questionColor); questionText.fontStyle = questionFontStyle; questionText.alignment = questionAlignment; questionText.characterSpacing = questionCharacterSpacing; // Unity 6 uyumlu yeni kullanım questionText.textWrappingMode = TextWrappingModes.NoWrap; questionText.overflowMode = TextOverflowModes.Overflow; } void UpdateLanguageUI() { if (languageText != null) languageText.text = "LANG: " + langNames[selectedLangIndex]; } void BindMainButtons() { if (submitButton != null) { submitButton.onClick.RemoveAllListeners(); submitButton.onClick.AddListener(OnSubmit); } if (extraTimeButton != null) { extraTimeButton.onClick.RemoveAllListeners(); extraTimeButton.onClick.AddListener(OnExtraTime); } if (extraHintButton != null) { extraHintButton.onClick.RemoveAllListeners(); extraHintButton.onClick.AddListener(OnExtraHint); } if (extraSkipButton != null) { extraSkipButton.onClick.RemoveAllListeners(); extraSkipButton.onClick.AddListener(OnExtraSkip); } if (backButton != null) { backButton.onClick.RemoveAllListeners(); backButton.onClick.AddListener(() => { SceneManager.LoadScene(backSceneName); }); } } void BindAnswerButtons() { for (int i = 0; i < answerButtons.Length; i++) { int index = i; if (answerButtons[i] == null) continue; answerButtons[i].onClick.RemoveAllListeners(); answerButtons[i].onClick.AddListener(() => { SelectAnswer(index); }); } } void HideCooldownTexts() { if (extraTimeCooldownText != null) extraTimeCooldownText.gameObject.SetActive(false); if (extraHintCooldownText != null) extraHintCooldownText.gameObject.SetActive(false); if (extraSkipCooldownText != null) extraSkipCooldownText.gameObject.SetActive(false); } void CacheButtonData() { int len = answerButtons.Length; originalButtonScales = new Vector3[len]; originalButtonPositions = new Vector3[len]; buttonScaleCoroutines = new Coroutine[len]; buttonEnterCoroutines = new Coroutine[len]; for (int i = 0; i < len; i++) { if (answerButtons[i] == null) continue; originalButtonScales[i] = answerButtons[i].transform.localScale; originalButtonPositions[i] = answerButtons[i].transform.localPosition; if (answerButtons[i].GetComponent() == null) answerButtons[i].gameObject.AddComponent(); } } void ForceButtonTransitionNone() { for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; answerButtons[i].transition = Selectable.Transition.None; Image img = answerButtons[i].image; if (img != null) img.color = FullAlpha(normalButtonColor); CanvasGroup cg = answerButtons[i].GetComponent(); if (cg != null) { cg.alpha = 1f; cg.interactable = true; cg.blocksRaycasts = true; } } } void PrepareFeedbackText() { if (feedbackText == null) return; feedbackText.alignment = TextAlignmentOptions.Center; feedbackText.fontSize = Mathf.Max(feedbackText.fontSize, 72f); feedbackText.text = ""; feedbackText.color = FullAlpha(feedbackText.color); } void NextQuestion() { if (gameOver) return; waitingNextQuestion = false; selectedAnswer = -1; ResetButtons(); if (questionIndex >= questionPool.Count) { questionIndex = 0; Shuffle(questionPool); } if (questionPool.Count > 1 && questionIndex == lastQuestionShown) { questionIndex++; if (questionIndex >= questionPool.Count) { questionIndex = 0; Shuffle(questionPool); } } lastQuestionShown = questionIndex; PronunciationEntry chosen = questionPool[questionIndex++]; string word = chosen.words[selectedLangIndex]; if (questionUppercase) word = word.ToUpper(); if (questionText != null) { questionText.text = word; StartCoroutine(QuestionEnterAnim(questionText)); } List options = new List(); for (int i = 0; i < 4; i++) options.Add(chosen.GetOption(selectedLangIndex, i)); string correctText = chosen.GetOption(selectedLangIndex, 0); Shuffle(options); correctAnswerIndex = options.IndexOf(correctText); for (int i = 0; i < 4; i++) { if (answerTexts[i] != null) answerTexts[i].text = options[i]; } BindAnswerButtons(); for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; if (buttonEnterCoroutines[i] != null) StopCoroutine(buttonEnterCoroutines[i]); buttonEnterCoroutines[i] = StartCoroutine(ButtonEnterAnim(answerButtons[i], i)); } } void ResetButtons() { for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; StopButtonCoroutines(i); answerButtons[i].interactable = true; answerButtons[i].transition = Selectable.Transition.None; answerButtons[i].transform.localRotation = Quaternion.identity; Image img = answerButtons[i].image; if (img != null) img.color = FullAlpha(normalButtonColor); answerButtons[i].transform.localScale = originalButtonScales[i]; answerButtons[i].transform.localPosition = originalButtonPositions[i]; CanvasGroup cg = answerButtons[i].GetComponent(); if (cg != null) { cg.alpha = 1f; cg.interactable = true; cg.blocksRaycasts = true; } } if (feedbackText != null) feedbackText.text = ""; if (submitButton != null) submitButton.interactable = true; } void SelectAnswer(int index) { if (gameOver || waitingNextQuestion) return; if (index < 0 || index >= answerButtons.Length) return; if (answerButtons[index] == null) return; if (!answerButtons[index].interactable) return; if (selectedAnswer == index) return; if (selectedAnswer >= 0) { Image oldImg = answerButtons[selectedAnswer].image; if (oldImg != null) oldImg.color = FullAlpha(normalButtonColor); StartScaleAnim(selectedAnswer, originalButtonScales[selectedAnswer]); } selectedAnswer = index; Image img = answerButtons[index].image; if (img != null) img.color = FullAlpha(selectedButtonColor); StartSelectPopAnim(index); } void OnSubmit() { if (gameOver || waitingNextQuestion) return; if (selectedAnswer == -1) { if (feedbackText != null) { feedbackText.color = FullAlpha(wrongButtonColor); feedbackText.text = "CHOOSE AN ANSWER"; StartCoroutine(FeedbackAnim(feedbackText)); } return; } waitingNextQuestion = true; if (submitButton != null) submitButton.interactable = false; LockAnswerButtons(); if (selectedAnswer == correctAnswerIndex) HandleCorrectAnswer(); else HandleWrongAnswer(); UpdateScore(); UpdateCombo(); StartCoroutine(DelayNext()); } void LockAnswerButtons() { for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; answerButtons[i].interactable = false; if (i != selectedAnswer && i != correctAnswerIndex) { Image img = answerButtons[i].image; if (img != null) img.color = FullAlpha(normalButtonColor); } } } void HandleCorrectAnswer() { Image img = answerButtons[selectedAnswer].image; if (img != null) img.color = FullAlpha(correctButtonColor); StartCoroutine(CorrectPulse(answerButtons[selectedAnswer], selectedAnswer)); combo++; correctCount++; if (combo > bestComboThisRun) bestComboThisRun = combo; score += Mathf.RoundToInt(basePoints * GetComboMultiplier(combo)); timeLeft += correctTimeBonus; if (feedbackText != null) { feedbackText.color = FullAlpha(correctButtonColor); feedbackText.text = combo >= 3 ? "CORRECT " + combo + "x COMBO!" : "CORRECT"; StartCoroutine(FeedbackAnim(feedbackText)); } if (comboText != null) StartCoroutine(ComboAnim(comboText)); if (scoreText != null) StartCoroutine(ScorePopAnim(scoreText)); } void HandleWrongAnswer() { Image wrongImg = answerButtons[selectedAnswer].image; if (wrongImg != null) wrongImg.color = FullAlpha(wrongButtonColor); StartCoroutine(ShakeButton(answerButtons[selectedAnswer], selectedAnswer)); if (correctAnswerIndex >= 0 && correctAnswerIndex < answerButtons.Length) { Image correctImg = answerButtons[correctAnswerIndex].image; if (correctImg != null) correctImg.color = FullAlpha(correctButtonColor); StartCoroutine(CorrectReveal(answerButtons[correctAnswerIndex], correctAnswerIndex)); } wrongCount++; combo = 0; score -= wrongPenalty; if (score < 0) score = 0; if (feedbackText != null) { feedbackText.color = FullAlpha(wrongButtonColor); if (correctAnswerIndex >= 0 && correctAnswerIndex < answerTexts.Length && answerTexts[correctAnswerIndex] != null) feedbackText.text = "WRONG\nCorrect: " + answerTexts[correctAnswerIndex].text; else feedbackText.text = "WRONG"; StartCoroutine(FeedbackAnim(feedbackText)); } } IEnumerator DelayNext() { yield return new WaitForSeconds(nextQuestionDelay); if (!gameOver) NextQuestion(); } void OnExtraTime() { if (!extraTimeReady || gameOver) return; timeLeft += extraTimeAmount; extraTimeReady = false; if (extraTimeButton != null) extraTimeButton.interactable = false; StartCoroutine(Cooldown(extraTimeCooldownText, extraTimeButton, () => extraTimeReady = true)); } void OnExtraHint() { if (!extraHintReady || gameOver || waitingNextQuestion) return; int removed = 0; for (int i = 0; i < answerButtons.Length && removed < 2; i++) { if (i == correctAnswerIndex) continue; if (selectedAnswer >= 0 && i == selectedAnswer) continue; if (answerButtons[i] == null) continue; if (!answerButtons[i].interactable) continue; answerButtons[i].interactable = false; Image img = answerButtons[i].image; if (img != null) img.color = FullAlpha(disabledButtonColor); if (answerTexts[i] != null) answerTexts[i].text = "—"; StartCoroutine(FadeOutButton(answerButtons[i])); removed++; } extraHintReady = false; if (extraHintButton != null) extraHintButton.interactable = false; StartCoroutine(Cooldown(extraHintCooldownText, extraHintButton, () => extraHintReady = true)); } void OnExtraSkip() { if (!extraSkipReady || gameOver || waitingNextQuestion) return; NextQuestion(); extraSkipReady = false; if (extraSkipButton != null) extraSkipButton.interactable = false; StartCoroutine(Cooldown(extraSkipCooldownText, extraSkipButton, () => extraSkipReady = true)); } IEnumerator Cooldown(TMP_Text label, Button button, System.Action done) { float t = cooldownDuration; if (label != null) label.gameObject.SetActive(true); while (t > 0f) { if (label != null) label.text = Mathf.CeilToInt(t).ToString(); t -= Time.deltaTime; yield return null; } if (label != null) { label.text = ""; label.gameObject.SetActive(false); } if (button != null) button.interactable = true; done?.Invoke(); } void GameOver() { if (gameOver) return; gameOver = true; PlayerPrefs.SetString("LastPlayedMod", modKey); PlayerPrefs.SetInt("Mod13Skor", score); PlayerPrefs.SetInt(modKey + "_LastScore", score); PlayerPrefs.SetInt(modKey + "_Correct", correctCount); PlayerPrefs.SetInt(modKey + "_Wrong", wrongCount); PlayerPrefs.SetInt(modKey + "_LastCombo", combo); PlayerPrefs.SetInt(modKey + "_BestComboRun", bestComboThisRun); PlayerPrefs.SetInt(modKey + "_LastTimeLeft", Mathf.CeilToInt(timeLeft)); int totalAnswers = correctCount + wrongCount; int accuracy = totalAnswers > 0 ? Mathf.RoundToInt((correctCount / (float)totalAnswers) * 100f) : 0; PlayerPrefs.SetInt(modKey + "_LastAccuracy", accuracy); if (score > PlayerPrefs.GetInt(modKey + "_MaxScore", 0)) PlayerPrefs.SetInt(modKey + "_MaxScore", score); if (bestComboThisRun > PlayerPrefs.GetInt(modKey + "_BestCombo", 0)) PlayerPrefs.SetInt(modKey + "_BestCombo", bestComboThisRun); PlayerPrefs.SetInt(modKey + "_TotalCorrect", PlayerPrefs.GetInt(modKey + "_TotalCorrect", 0) + correctCount); PlayerPrefs.SetInt(modKey + "_TotalWrong", PlayerPrefs.GetInt(modKey + "_TotalWrong", 0) + wrongCount); PlayerPrefs.SetInt(modKey + "_PlayCount", PlayerPrefs.GetInt(modKey + "_PlayCount", 0) + 1); PlayerPrefs.SetInt("TotalScore", PlayerPrefs.GetInt("TotalScore", 0) + score); int prevBest = PlayerPrefs.GetInt("Mod13Score", 0); if (score > prevBest) PlayerPrefs.SetInt("Mod13Score", score); PlayerPrefs.SetInt("Mod13LastScore", score); PlayerPrefs.SetInt("Mod13Correct", correctCount); PlayerPrefs.SetInt("Mod13Wrong", wrongCount); PlayerPrefs.Save(); SceneManager.LoadScene(resultSceneName); } void UpdateScore() { if (scoreText != null) scoreText.text = score.ToString(); } void UpdateCombo() { if (comboText != null) comboText.text = combo + "x"; } void UpdateTimerUI() { if (timerText == null) return; timerText.text = Mathf.CeilToInt(timeLeft).ToString(); if (timeLeft <= 10f) { timerText.color = FullAlpha(Color.Lerp(timerWarnColor, Color.white, Mathf.PingPong(Time.time * 3f, 1f))); timerText.transform.localScale = Vector3.one * (1f + Mathf.Sin(Time.time * 10f) * 0.05f); } else { timerText.color = FullAlpha(timerNormalColor); timerText.transform.localScale = Vector3.one; } } float GetComboMultiplier(int c) { if (c >= 8) return mult_8plus; if (c >= 5) return mult_5to7; if (c >= 3) return mult_3to4; return mult_1to2; } void StartScaleAnim(int index, Vector3 targetScale) { if (index < 0 || index >= answerButtons.Length) return; if (answerButtons[index] == null) return; if (buttonScaleCoroutines[index] != null) StopCoroutine(buttonScaleCoroutines[index]); buttonScaleCoroutines[index] = StartCoroutine(ScaleButton(answerButtons[index], targetScale)); } void StartSelectPopAnim(int index) { if (index < 0 || index >= answerButtons.Length) return; if (answerButtons[index] == null) return; if (buttonScaleCoroutines[index] != null) StopCoroutine(buttonScaleCoroutines[index]); buttonScaleCoroutines[index] = StartCoroutine(SelectPop(answerButtons[index], index)); } void StopButtonCoroutines(int index) { if (buttonScaleCoroutines != null && index < buttonScaleCoroutines.Length && buttonScaleCoroutines[index] != null) { StopCoroutine(buttonScaleCoroutines[index]); buttonScaleCoroutines[index] = null; } if (buttonEnterCoroutines != null && index < buttonEnterCoroutines.Length && buttonEnterCoroutines[index] != null) { StopCoroutine(buttonEnterCoroutines[index]); buttonEnterCoroutines[index] = null; } } IEnumerator ScaleButton(Button btn, Vector3 targetScale) { Vector3 startScale = btn.transform.localScale; float t = 0f; while (t < 1f) { t += Time.deltaTime / Mathf.Max(0.01f, selectAnimTime); btn.transform.localScale = Vector3.Lerp(startScale, targetScale, EaseOutCubic(t)); yield return null; } btn.transform.localScale = targetScale; } IEnumerator SelectPop(Button btn, int idx) { Vector3 baseScale = originalButtonScales[idx]; Vector3 target = baseScale * selectedScale; Vector3 pop = baseScale * 1.06f; Vector3 start = btn.transform.localScale; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.09f; btn.transform.localScale = Vector3.Lerp(start, pop, EaseOutCubic(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.10f; btn.transform.localScale = Vector3.Lerp(pop, target, EaseOutBack(t)); yield return null; } btn.transform.localScale = target; } IEnumerator ButtonEnterAnim(Button btn, int idx) { CanvasGroup cg = btn.GetComponent(); Vector3 targetPos = originalButtonPositions[idx]; Vector3 startPos = targetPos + Vector3.up * 45f; Vector3 targetScale = originalButtonScales[idx]; Vector3 startScale = targetScale * 0.80f; btn.transform.localPosition = startPos; btn.transform.localScale = startScale; if (cg != null) cg.alpha = 0f; yield return new WaitForSeconds(idx * enterStagger); float t = 0f; while (t < 1f) { t += Time.deltaTime / Mathf.Max(0.01f, enterAnimTime); float easePos = EaseOutBack(t); float easeFade = EaseOutCubic(Mathf.Clamp01(t * 1.4f)); btn.transform.localPosition = Vector3.Lerp(startPos, targetPos, easePos); btn.transform.localScale = Vector3.Lerp(startScale, targetScale, easePos); if (cg != null) cg.alpha = easeFade; yield return null; } btn.transform.localPosition = targetPos; btn.transform.localScale = targetScale; if (cg != null) cg.alpha = 1f; } IEnumerator CorrectPulse(Button btn, int idx) { Vector3 normal = originalButtonScales[idx]; Vector3 big = normal * 1.16f; Vector3 mid = normal * 1.05f; float t = 0f; Vector3 start = btn.transform.localScale; while (t < 1f) { t += Time.deltaTime / 0.11f; btn.transform.localScale = Vector3.Lerp(start, big, EaseOutBack(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.10f; btn.transform.localScale = Vector3.Lerp(big, mid, EaseOutCubic(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.10f; btn.transform.localScale = Vector3.Lerp(mid, normal, EaseOutCubic(t)); yield return null; } btn.transform.localScale = normal; } IEnumerator CorrectReveal(Button btn, int idx) { Vector3 normal = originalButtonScales[idx]; Vector3 big = normal * 1.10f; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.18f; btn.transform.localScale = Vector3.Lerp(normal, big, EaseOutBack(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.16f; btn.transform.localScale = Vector3.Lerp(big, normal, EaseOutCubic(t)); yield return null; } btn.transform.localScale = normal; } IEnumerator ShakeButton(Button btn, int idx) { Vector3 origin = originalButtonPositions[idx]; Quaternion originRot = btn.transform.localRotation; float duration = 0.40f; float magnitude = 16f; float elapsed = 0f; while (elapsed < duration) { float power = 1f - (elapsed / duration); power *= power; float wave = Mathf.Sin(elapsed * 55f); float x = wave * magnitude * power; float rot = wave * 4f * power; btn.transform.localPosition = origin + new Vector3(x, 0f, 0f); btn.transform.localRotation = Quaternion.Euler(0f, 0f, rot); elapsed += Time.deltaTime; yield return null; } btn.transform.localPosition = origin; btn.transform.localRotation = originRot; } IEnumerator FadeOutButton(Button btn) { CanvasGroup cg = btn.GetComponent(); if (cg == null) yield break; float start = cg.alpha; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.20f; cg.alpha = Mathf.Lerp(start, 0.35f, EaseOutCubic(t)); yield return null; } cg.alpha = 0.35f; } IEnumerator QuestionEnterAnim(TMP_Text txt) { Color c = FullAlpha(txt.color); Vector3 baseScale = Vector3.one; Vector3 startScale = baseScale * 0.85f; c.a = 0f; txt.color = c; txt.transform.localScale = startScale; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.22f; c.a = EaseOutCubic(t); txt.color = c; txt.transform.localScale = Vector3.Lerp(startScale, baseScale, EaseOutBack(t)); yield return null; } c.a = 1f; txt.color = c; txt.transform.localScale = baseScale; } IEnumerator FeedbackAnim(TMP_Text txt) { Vector3 originalScale = Vector3.one; Vector3 smallScale = originalScale * 0.8f; Vector3 bigScale = originalScale * 1.20f; Color c = FullAlpha(txt.color); c.a = 0f; txt.color = c; txt.transform.localScale = smallScale; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.16f; txt.transform.localScale = Vector3.Lerp(smallScale, bigScale, EaseOutBack(t)); c.a = Mathf.Clamp01(t * 1.5f); txt.color = c; yield return null; } c.a = 1f; txt.color = c; t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; txt.transform.localScale = Vector3.Lerp(bigScale, originalScale, EaseOutCubic(t)); yield return null; } txt.transform.localScale = originalScale; } IEnumerator ComboAnim(TMP_Text txt) { Vector3 original = Vector3.one; Vector3 big = original * 1.25f; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; txt.transform.localScale = Vector3.Lerp(original, big, EaseOutBack(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.14f; txt.transform.localScale = Vector3.Lerp(big, original, EaseOutCubic(t)); yield return null; } txt.transform.localScale = original; } IEnumerator ScorePopAnim(TMP_Text txt) { Vector3 original = Vector3.one; Vector3 big = original * 1.18f; float t = 0f; while (t < 1f) { t += Time.deltaTime / 0.10f; txt.transform.localScale = Vector3.Lerp(original, big, EaseOutBack(t)); yield return null; } t = 0f; while (t < 1f) { t += Time.deltaTime / 0.12f; txt.transform.localScale = Vector3.Lerp(big, original, EaseOutCubic(t)); yield return null; } txt.transform.localScale = original; } Color FullAlpha(Color c) { c.a = 1f; return c; } void Shuffle(List list) { for (int i = 0; i < list.Count; i++) { int rnd = Random.Range(i, list.Count); T temp = list[i]; list[i] = list[rnd]; list[rnd] = temp; } } float EaseOutBack(float t) { t = Mathf.Clamp01(t); float c1 = 1.70158f; float c3 = c1 + 1f; return 1f + c3 * Mathf.Pow(t - 1f, 3f) + c1 * Mathf.Pow(t - 1f, 2f); } float EaseOutCubic(float t) { t = Mathf.Clamp01(t); return 1f - Mathf.Pow(1f - t, 3f); } }