starOfWords/Assets/Scripts/4.0/game/Pronunciation13Mod.cs
portakal ecb1c9edea Initial commit: Star of Words Unity project
Snapshot of the existing Unity 6 language-learning word game before any
refactoring. Adds Unity .gitignore and .gitattributes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:31:20 +03:00

1369 lines
No EOL
35 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<PronunciationEntry> questionPool = new List<PronunciationEntry>();
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<string> SplitCsvLine(string line)
{
List<string> result = new List<string>();
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<CanvasGroup>() == null)
answerButtons[i].gameObject.AddComponent<CanvasGroup>();
}
}
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<CanvasGroup>();
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<string> options = new List<string>();
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<CanvasGroup>();
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<CanvasGroup>();
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<CanvasGroup>();
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<T>(List<T> 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);
}
}