using UnityEngine; using UnityEngine.UI; using TMPro; using UnityEngine.SceneManagement; using System.Collections; using System.Collections.Generic; public class SpeedReview30Mod : MonoBehaviour { [Header("DATA")] public TextAsset wordFile; public string wordsResourcePath = "Mod30SpeedReviewWords"; [Header("MAIN TEXTS")] [Tooltip("Ekrandaki ana soru burada gösterilecek.")] public TMP_Text questionText; [Tooltip("Kısa görev açıklaması burada gösterilecek.")] public TMP_Text instructionText; [Header("UI TEXTS")] public TMP_Text titleText; public TMP_Text languageText; public TMP_Text feedbackText; public TMP_Text scoreText; public TMP_Text comboText; public TMP_Text timerText; public TMP_Text speedBonusText; [Header("ANSWER BUTTONS")] public Button[] answerButtons; public TMP_Text[] answerTexts; [Header("MAIN BUTTONS")] public Button submitButton; public Button backButton; [Header("SCENES")] public string backSceneName = "modsec 30"; public string resultSceneName = "modres 30"; [Header("LANGUAGE TEST")] [Tooltip("-1 otomatik. 0 TR, 1 EN, 2 ES, 3 DE, 4 FR, 5 PT | QuestionLangID = ekrandaki soru dili")] public int forcedQuestionLanguageIndex = -1; [Tooltip("-1 otomatik. 0 TR, 1 EN, 2 ES, 3 DE, 4 FR, 5 PT | AnswerLangID = şıklardaki cevap dili")] public int forcedAnswerLanguageIndex = -1; [Header("SPEED REVIEW SETTINGS")] [Tooltip("Speed Review modu sabit 30 saniye için tasarlandı.")] public float startTime = 30f; public int basePoints = 50; public int wrongPenalty = 0; public float nextQuestionDelay = 0.45f; [Header("SPEED BONUS")] [Tooltip("Bu süreden hızlı cevap verilirse büyük hız bonusu gelir.")] public float perfectSpeedTime = 2f; [Tooltip("Bu süreden hızlı cevap verilirse küçük hız bonusu gelir.")] public float goodSpeedTime = 4f; public int perfectSpeedBonus = 30; public int goodSpeedBonus = 15; [Header("COMBO MULTIPLIERS")] 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); readonly string[] langNames = { "Türkçe", "English", "Español", "Deutsch", "Français", "Português" }; const string modKey = "Mod30"; class SpeedEntry { public string[] questions = new string[6]; public string[] instructions = new string[6]; // Her dil için 4 cevap şıkkı: // 0 doğru // 1 yanlış // 2 yanlış // 3 yanlış public string[] options = new string[24]; } readonly List allQuestions = new List(); readonly List questionPool = new List(); SpeedEntry currentQuestion; int questionLangIndex = 1; int answerLangIndex = 0; int selectedAnswer = -1; int correctAnswerIndex = -1; int score = 0; int combo = 0; int correctCount = 0; int wrongCount = 0; int bestComboThisRun = 0; int totalSpeedBonus = 0; int lastSpeedBonus = 0; float timeLeft; float questionStartTime; bool gameOver = false; bool waitingNextQuestion = false; Vector3[] originalButtonScales; Vector3[] originalButtonPositions; void Awake() { LoadQuestions(); } void Start() { ResolveLanguages(); // Speed Review mantığı: sabit 30 saniye. timeLeft = startTime; if (timeLeft <= 0f) timeLeft = 30f; if (allQuestions.Count < 1) { Debug.LogError("Mod30 Speed Review: En az 1 geçerli TXT satırı gerekli. Yüklenen soru sayısı: " + allQuestions.Count); enabled = false; return; } if (questionText == null) { Debug.LogError("Mod30 Speed Review: questionText atanmadı."); enabled = false; return; } if (answerButtons == null || answerButtons.Length < 4 || answerTexts == null || answerTexts.Length < 4) { Debug.LogError("Mod30 Speed Review: 4 answerButtons ve 4 answerTexts atanmalı."); enabled = false; return; } PrepareUI(); BindButtons(); ResetQuestionPool(); UpdateLanguageUI(); UpdateScoreUI(); UpdateComboUI(); UpdateTimerUI(); UpdateSpeedBonusUI(); NextQuestion(); Debug.Log("Mod30 Soru Dili: " + questionLangIndex + " / " + langNames[questionLangIndex]); Debug.Log("Mod30 Şık Dili: " + answerLangIndex + " / " + langNames[answerLangIndex]); } void Update() { if (gameOver) return; timeLeft -= Time.deltaTime; if (timeLeft <= 0f) { timeLeft = 0f; UpdateTimerUI(); GameOver(); return; } UpdateTimerUI(); } // TXT FORMAT: // 0-5 soru: // trQuestion|enQuestion|esQuestion|deQuestion|frQuestion|ptQuestion // // 6-11 görev / açıklama: // trInstruction|enInstruction|esInstruction|deInstruction|frInstruction|ptInstruction // // 12-35 cevap şıkları: // trCorrect|trWrong1|trWrong2|trWrong3 // enCorrect|enWrong1|enWrong2|enWrong3 // esCorrect|esWrong1|esWrong2|esWrong3 // deCorrect|deWrong1|deWrong2|deWrong3 // frCorrect|frWrong1|frWrong2|frWrong3 // ptCorrect|ptWrong1|ptWrong2|ptWrong3 void LoadQuestions() { allQuestions.Clear(); TextAsset txt = wordFile; if (txt == null) txt = Resources.Load(wordsResourcePath); if (txt == null) { Debug.LogError("Mod30 Speed Review: TXT bulunamadı. Şuraya koy: Assets/Resources/" + wordsResourcePath + ".txt"); return; } string[] lines = txt.text.Split( new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries ); for (int i = 0; i < lines.Length; i++) { string line = lines[i].Trim(); if (string.IsNullOrWhiteSpace(line)) continue; if (line.StartsWith("#")) continue; string[] p = SplitLineSmart(line); if (p.Length < 36) { Debug.LogWarning("Mod30 Speed Review TXT eksik satır atlandı. Satır: " + (i + 1) + " Alan sayısı: " + p.Length); continue; } SpeedEntry entry = new SpeedEntry(); bool valid = true; for (int l = 0; l < 6; l++) { entry.questions[l] = CleanField(p[l]); entry.instructions[l] = CleanField(p[6 + l]); if (string.IsNullOrWhiteSpace(entry.questions[l])) valid = false; if (string.IsNullOrWhiteSpace(entry.instructions[l])) valid = false; } for (int q = 0; q < 24; q++) { entry.options[q] = CleanField(p[12 + q]); if (string.IsNullOrWhiteSpace(entry.options[q])) valid = false; } if (valid) allQuestions.Add(entry); } Debug.Log("Mod30 Speed Review yüklenen soru sayısı: " + allQuestions.Count); } string[] SplitLineSmart(string line) { if (line.Contains("|")) return line.Split('|'); if (line.Contains("\t")) return line.Split('\t'); return line.Split(','); } string CleanField(string s) { if (string.IsNullOrEmpty(s)) return ""; return s.Trim().Trim('"').Trim().Replace("\\n", "\n").Replace("[blank]", "____"); } void ResolveLanguages() { if (forcedQuestionLanguageIndex >= 0 && forcedQuestionLanguageIndex <= 5) questionLangIndex = forcedQuestionLanguageIndex; else questionLangIndex = PlayerPrefs.GetInt("QuestionLangID", 1); if (forcedAnswerLanguageIndex >= 0 && forcedAnswerLanguageIndex <= 5) answerLangIndex = forcedAnswerLanguageIndex; else answerLangIndex = PlayerPrefs.GetInt("AnswerLangID", 0); questionLangIndex = ClampLanguage(questionLangIndex, 1); answerLangIndex = ClampLanguage(answerLangIndex, 0); } int ClampLanguage(int value, int fallback) { if (value < 0 || value > 5) return fallback; return value; } public void RefreshLanguagesFromPrefs() { ResolveLanguages(); UpdateLanguageUI(); if (currentQuestion != null && !gameOver) RefreshCurrentQuestion(); } public void SetQuestionLanguage(int id) { if (id < 0 || id > 5) return; questionLangIndex = id; PlayerPrefs.SetInt("QuestionLangID", id); PlayerPrefs.SetString("QuestionLangCode", ToCode(id)); PlayerPrefs.Save(); UpdateLanguageUI(); RefreshCurrentQuestion(); } public void SetAnswerLanguage(int id) { if (id < 0 || id > 5) return; answerLangIndex = id; PlayerPrefs.SetInt("AnswerLangID", id); PlayerPrefs.SetString("AnswerLangCode", ToCode(id)); PlayerPrefs.Save(); UpdateLanguageUI(); RefreshCurrentQuestion(); } string ToCode(int id) { switch (id) { case 0: return "tr"; case 1: return "en"; case 2: return "es"; case 3: return "de"; case 4: return "fr"; case 5: return "pt"; default: return "en"; } } void UpdateLanguageUI() { if (languageText != null) { languageText.text = "SORU: " + langNames[questionLangIndex] + " | CEVAP: " + langNames[answerLangIndex]; } if (titleText != null) titleText.text = GetTitleText(); } string GetTitleText() { switch (answerLangIndex) { case 0: return "Hızlı Tekrar"; case 1: return "Speed Review"; case 2: return "Repaso rápido"; case 3: return "Schnelle Wiederholung"; case 4: return "Révision rapide"; case 5: return "Revisão rápida"; default: return "Speed Review"; } } string GetText(string key) { int lang = answerLangIndex; if (key == "choose") { if (lang == 0) return "BİR CEVAP SEÇ"; if (lang == 1) return "CHOOSE AN ANSWER"; if (lang == 2) return "ELIGE UNA RESPUESTA"; if (lang == 3) return "WÄHLE EINE ANTWORT"; if (lang == 4) return "CHOISIS UNE RÉPONSE"; if (lang == 5) return "ESCOLHA UMA RESPOSTA"; } if (key == "correct") { if (lang == 0) return "DOĞRU"; if (lang == 1) return "CORRECT"; if (lang == 2) return "CORRECTO"; if (lang == 3) return "RICHTIG"; if (lang == 4) return "CORRECT"; if (lang == 5) return "CORRETO"; } if (key == "wrong") { if (lang == 0) return "YANLIŞ"; if (lang == 1) return "WRONG"; if (lang == 2) return "INCORRECTO"; if (lang == 3) return "FALSCH"; if (lang == 4) return "FAUX"; if (lang == 5) return "ERRADO"; } if (key == "correctAnswer") { if (lang == 0) return "Doğru cevap"; if (lang == 1) return "Correct answer"; if (lang == 2) return "Respuesta correcta"; if (lang == 3) return "Richtige Antwort"; if (lang == 4) return "Bonne réponse"; if (lang == 5) return "Resposta correta"; } if (key == "speed") { if (lang == 0) return "HIZ BONUSU"; if (lang == 1) return "SPEED BONUS"; if (lang == 2) return "BONO DE VELOCIDAD"; if (lang == 3) return "TEMPOBONUS"; if (lang == 4) return "BONUS DE VITESSE"; if (lang == 5) return "BÔNUS DE VELOCIDADE"; } if (key == "noSpeed") { if (lang == 0) return "Hız bonusu yok"; if (lang == 1) return "No speed bonus"; if (lang == 2) return "Sin bono de velocidad"; if (lang == 3) return "Kein Tempobonus"; if (lang == 4) return "Pas de bonus de vitesse"; if (lang == 5) return "Sem bônus de velocidade"; } return key; } void PrepareUI() { if (questionText != null) { questionText.alignment = TextAlignmentOptions.Center; questionText.textWrappingMode = TextWrappingModes.Normal; } if (instructionText != null) { instructionText.alignment = TextAlignmentOptions.Center; instructionText.textWrappingMode = TextWrappingModes.Normal; } if (feedbackText != null) { feedbackText.text = ""; feedbackText.alignment = TextAlignmentOptions.Center; feedbackText.textWrappingMode = TextWrappingModes.Normal; } originalButtonScales = new Vector3[answerButtons.Length]; originalButtonPositions = new Vector3[answerButtons.Length]; for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; originalButtonScales[i] = answerButtons[i].transform.localScale; originalButtonPositions[i] = answerButtons[i].transform.localPosition; answerButtons[i].transition = Selectable.Transition.None; if (answerButtons[i].image != null) answerButtons[i].image.color = FullAlpha(normalButtonColor); } } void BindButtons() { if (submitButton != null) { submitButton.onClick.RemoveAllListeners(); submitButton.onClick.AddListener(OnSubmit); } if (backButton != null) { backButton.onClick.RemoveAllListeners(); backButton.onClick.AddListener(() => { SaveProgressOnly(); SceneManager.LoadScene(backSceneName); }); } 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 ResetQuestionPool() { questionPool.Clear(); questionPool.AddRange(allQuestions); Shuffle(questionPool); } void NextQuestion() { if (gameOver) return; if (questionPool.Count <= 0) ResetQuestionPool(); currentQuestion = questionPool[0]; questionPool.RemoveAt(0); RefreshCurrentQuestion(); } void RefreshCurrentQuestion() { if (currentQuestion == null) return; waitingNextQuestion = false; selectedAnswer = -1; correctAnswerIndex = -1; lastSpeedBonus = 0; ResetButtons(); string questionValue = currentQuestion.questions[questionLangIndex]; string instructionValue = currentQuestion.instructions[questionLangIndex]; string correctAnswer = GetOption(currentQuestion, answerLangIndex, 0); if (questionText != null) questionText.text = questionValue; if (instructionText != null) instructionText.text = instructionValue; List options = new List(); for (int i = 0; i < 4; i++) { string option = GetOption(currentQuestion, answerLangIndex, i); if (!string.IsNullOrWhiteSpace(option)) options.Add(option); } while (options.Count < 4) options.Add("?"); Shuffle(options); correctAnswerIndex = options.IndexOf(correctAnswer); if (correctAnswerIndex < 0) correctAnswerIndex = 0; for (int i = 0; i < 4; i++) { if (answerTexts != null && i < answerTexts.Length && answerTexts[i] != null) answerTexts[i].text = options[i]; } if (feedbackText != null) feedbackText.text = ""; if (submitButton != null) submitButton.interactable = true; questionStartTime = Time.time; UpdateSpeedBonusUI(); StartCoroutine(QuestionPopAnim()); } string GetOption(SpeedEntry entry, int langIndex, int optionIndex) { int index = langIndex * 4 + optionIndex; if (index < 0 || index >= entry.options.Length) return ""; return entry.options[index]; } void ResetButtons() { for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; answerButtons[i].interactable = true; answerButtons[i].transform.localScale = originalButtonScales[i]; answerButtons[i].transform.localPosition = originalButtonPositions[i]; if (answerButtons[i].image != null) answerButtons[i].image.color = FullAlpha(normalButtonColor); if (answerTexts != null && i < answerTexts.Length && answerTexts[i] != null) answerTexts[i].text = ""; } } void SelectAnswer(int index) { if (gameOver || waitingNextQuestion) return; selectedAnswer = index; for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] == null) continue; if (answerButtons[i].image != null) answerButtons[i].image.color = FullAlpha(normalButtonColor); answerButtons[i].transform.localScale = originalButtonScales[i]; } if (answerButtons[index].image != null) answerButtons[index].image.color = FullAlpha(selectedButtonColor); answerButtons[index].transform.localScale = originalButtonScales[index] * 0.94f; } void OnSubmit() { if (gameOver || waitingNextQuestion) return; if (selectedAnswer < 0) { ShowFeedback(GetText("choose"), wrongButtonColor); return; } waitingNextQuestion = true; if (submitButton != null) submitButton.interactable = false; for (int i = 0; i < answerButtons.Length; i++) { if (answerButtons[i] != null) answerButtons[i].interactable = false; } if (selectedAnswer == correctAnswerIndex) CorrectAnswer(); else WrongAnswer(); UpdateScoreUI(); UpdateComboUI(); UpdateSpeedBonusUI(); SaveProgressOnly(); StartCoroutine(NextQuestionDelay()); } void CorrectAnswer() { if (answerButtons[selectedAnswer].image != null) answerButtons[selectedAnswer].image.color = FullAlpha(correctButtonColor); combo++; correctCount++; if (combo > bestComboThisRun) bestComboThisRun = combo; float answerTime = Time.time - questionStartTime; lastSpeedBonus = CalculateSpeedBonus(answerTime); totalSpeedBonus += lastSpeedBonus; int comboPoints = Mathf.RoundToInt(basePoints * GetComboMultiplier(combo)); int earned = comboPoints + lastSpeedBonus; score += earned; string bonusText = ""; if (lastSpeedBonus > 0) bonusText = "\n" + GetText("speed") + " +" + lastSpeedBonus; else bonusText = "\n" + GetText("noSpeed"); ShowFeedback(GetText("correct") + " +" + earned + bonusText, correctButtonColor); StartCoroutine(ButtonPop(answerButtons[selectedAnswer])); } void WrongAnswer() { if (answerButtons[selectedAnswer].image != null) answerButtons[selectedAnswer].image.color = FullAlpha(wrongButtonColor); if (correctAnswerIndex >= 0 && correctAnswerIndex < answerButtons.Length) { if (answerButtons[correctAnswerIndex].image != null) answerButtons[correctAnswerIndex].image.color = FullAlpha(correctButtonColor); } wrongCount++; combo = 0; lastSpeedBonus = 0; score -= wrongPenalty; if (score < 0) score = 0; string correctText = ""; if (correctAnswerIndex >= 0 && correctAnswerIndex < answerTexts.Length && answerTexts[correctAnswerIndex] != null) correctText = answerTexts[correctAnswerIndex].text; ShowFeedback(GetText("wrong") + "\n" + GetText("correctAnswer") + ": " + correctText, wrongButtonColor); } int CalculateSpeedBonus(float answerTime) { if (answerTime <= perfectSpeedTime) return perfectSpeedBonus; if (answerTime <= goodSpeedTime) return goodSpeedBonus; return 0; } IEnumerator NextQuestionDelay() { yield return new WaitForSeconds(nextQuestionDelay); if (!gameOver) NextQuestion(); } void SaveProgressOnly() { PlayerPrefs.SetInt("Mod30Skor", score); PlayerPrefs.SetInt("Mod30Score", score); PlayerPrefs.SetInt("Mod30Correct", correctCount); PlayerPrefs.SetInt("Mod30Wrong", wrongCount); PlayerPrefs.SetInt("Mod30SpeedBonus", totalSpeedBonus); 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)); PlayerPrefs.SetInt(modKey + "_TotalSpeedBonus", totalSpeedBonus); int total = correctCount + wrongCount; int accuracy = total > 0 ? Mathf.RoundToInt((correctCount / (float)total) * 100f) : 0; PlayerPrefs.SetInt(modKey + "_LastAccuracy", accuracy); if (score > PlayerPrefs.GetInt(modKey + "_MaxScore", 0)) PlayerPrefs.SetInt(modKey + "_MaxScore", score); if (score > PlayerPrefs.GetInt("Mod30LeaderScore", 0)) PlayerPrefs.SetInt("Mod30LeaderScore", score); if (score > PlayerPrefs.GetInt("Mod30BestScore", 0)) PlayerPrefs.SetInt("Mod30BestScore", score); if (bestComboThisRun > PlayerPrefs.GetInt(modKey + "_BestCombo", 0)) PlayerPrefs.SetInt(modKey + "_BestCombo", bestComboThisRun); PlayerPrefs.Save(); } void GameOver() { if (gameOver) return; gameOver = true; SaveProgressOnly(); PlayerPrefs.SetString("LastPlayedMod", modKey); PlayerPrefs.SetInt(modKey + "_TotalCorrect", PlayerPrefs.GetInt(modKey + "_TotalCorrect", 0) + correctCount); PlayerPrefs.SetInt(modKey + "_TotalWrong", PlayerPrefs.GetInt(modKey + "_TotalWrong", 0) + wrongCount); PlayerPrefs.SetInt(modKey + "_TotalSpeedBonusAllTime", PlayerPrefs.GetInt(modKey + "_TotalSpeedBonusAllTime", 0) + totalSpeedBonus); PlayerPrefs.SetInt(modKey + "_PlayCount", PlayerPrefs.GetInt(modKey + "_PlayCount", 0) + 1); PlayerPrefs.SetInt("TotalScore", PlayerPrefs.GetInt("TotalScore", 0) + score); PlayerPrefs.Save(); SceneManager.LoadScene(resultSceneName); } void UpdateScoreUI() { if (scoreText != null) scoreText.text = score.ToString(); } void UpdateComboUI() { if (comboText != null) comboText.text = combo + "x"; } void UpdateSpeedBonusUI() { if (speedBonusText != null) speedBonusText.text = "+" + lastSpeedBonus; } void UpdateTimerUI() { if (timerText == null) return; timerText.text = Mathf.CeilToInt(timeLeft).ToString(); if (timeLeft <= 5f) timerText.color = Color.red; else timerText.color = Color.white; } void ShowFeedback(string msg, Color color) { if (feedbackText == null) return; feedbackText.text = msg; feedbackText.color = FullAlpha(color); StopCoroutine(nameof(FeedbackAnim)); StartCoroutine(nameof(FeedbackAnim)); } 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; } IEnumerator FeedbackAnim() { if (feedbackText == null) yield break; Vector3 normal = Vector3.one; Vector3 big = Vector3.one * 1.15f; feedbackText.transform.localScale = normal; float t = 0; while (t < 1) { t += Time.deltaTime / 0.15f; feedbackText.transform.localScale = Vector3.Lerp(normal, big, t); yield return null; } t = 0; while (t < 1) { t += Time.deltaTime / 0.15f; feedbackText.transform.localScale = Vector3.Lerp(big, normal, t); yield return null; } feedbackText.transform.localScale = normal; } IEnumerator ButtonPop(Button btn) { if (btn == null) yield break; Vector3 normal = btn.transform.localScale; Vector3 big = normal * 1.12f; float t = 0; while (t < 1) { t += Time.deltaTime / 0.12f; btn.transform.localScale = Vector3.Lerp(normal, big, t); yield return null; } t = 0; while (t < 1) { t += Time.deltaTime / 0.12f; btn.transform.localScale = Vector3.Lerp(big, normal, t); yield return null; } btn.transform.localScale = normal; } IEnumerator QuestionPopAnim() { if (questionText == null) yield break; Vector3 normal = Vector3.one; Vector3 small = Vector3.one * 0.92f; questionText.transform.localScale = small; float t = 0; while (t < 1) { t += Time.deltaTime / 0.15f; questionText.transform.localScale = Vector3.Lerp(small, normal, t); yield return null; } questionText.transform.localScale = normal; } 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; } } }