// ============================================== // QuizManager.cs - Quiz-Steuerung // ============================================== // Verwaltet Quick-Fragen (waehrend Fahrt) und // Pause-Fragen (Spiel haelt an). using System; using System.Collections; using UnityEngine; using UnityEngine.UI; using TMPro; namespace BreakpilotDrive { public class QuizManager : MonoBehaviour { public static QuizManager Instance { get; private set; } [Header("UI Referenzen")] [SerializeField] private GameObject quickQuizPanel; [SerializeField] private GameObject pauseQuizPanel; [SerializeField] private TextMeshProUGUI questionText; [SerializeField] private Button[] answerButtons; [SerializeField] private TextMeshProUGUI timerText; [SerializeField] private Slider timerSlider; [Header("Audio (fuer Audio-Version)")] [SerializeField] private AudioSource audioSource; [SerializeField] private bool isAudioMode = false; [Header("Einstellungen")] [SerializeField] private int pointsCorrect = 500; [SerializeField] private int pointsWrong = -100; [SerializeField] private float pauseQuestionInterval = 500f; // Alle X Meter // Aktueller Zustand private QuizQuestion currentQuestion; private bool isQuizActive = false; private float timeRemaining; private Coroutine timerCoroutine; // Events public event Action OnQuestionAnswered; // (correct, points) public event Action OnQuizStarted; public event Action OnQuizEnded; // Statistik private int questionsAnswered = 0; private int questionsCorrect = 0; void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } void Start() { // UI initial verstecken if (quickQuizPanel) quickQuizPanel.SetActive(false); if (pauseQuizPanel) pauseQuizPanel.SetActive(false); // Answer Buttons einrichten for (int i = 0; i < answerButtons.Length; i++) { int index = i; // Capture fuer Lambda answerButtons[i].onClick.AddListener(() => OnAnswerSelected(index)); } } void Update() { // Keyboard-Eingabe fuer Antworten (1-4) if (isQuizActive && currentQuestion != null) { if (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1)) OnAnswerSelected(0); else if (Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2)) OnAnswerSelected(1); else if (Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3)) OnAnswerSelected(2); else if (Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4)) OnAnswerSelected(3); } } // ============================================== // Quick Quiz (waehrend der Fahrt) // ============================================== public void ShowQuickQuestion(string visualTrigger) { if (isQuizActive) return; // Frage aus Cache holen int difficulty = BreakpilotAPI.Instance != null ? (int)GetCurrentDifficulty() : 3; QuizQuestion question = BreakpilotAPI.Instance?.GetQuestionForTrigger(visualTrigger, difficulty); if (question == null) { Debug.LogWarning($"Keine Frage fuer Trigger '{visualTrigger}' gefunden"); return; } currentQuestion = question; isQuizActive = true; // UI anzeigen if (quickQuizPanel) quickQuizPanel.SetActive(true); DisplayQuestion(question); // Timer starten float timeLimit = question.time_limit_seconds > 0 ? question.time_limit_seconds : 5f; timerCoroutine = StartCoroutine(QuickQuizTimer(timeLimit)); // Audio-Version: Frage vorlesen if (isAudioMode) { SpeakQuestion(question); } OnQuizStarted?.Invoke(); } private IEnumerator QuickQuizTimer(float duration) { timeRemaining = duration; while (timeRemaining > 0) { timeRemaining -= Time.deltaTime; // UI updaten if (timerText) timerText.text = $"{timeRemaining:F1}s"; if (timerSlider) timerSlider.value = timeRemaining / duration; yield return null; } // Zeit abgelaufen = falsche Antwort OnAnswerSelected(-1); } // ============================================== // Pause Quiz (Spiel haelt an) // ============================================== public void ShowPauseQuestion(QuizQuestion question) { if (isQuizActive) return; currentQuestion = question; isQuizActive = true; // Spiel pausieren Time.timeScale = 0; // UI anzeigen if (pauseQuizPanel) pauseQuizPanel.SetActive(true); DisplayQuestion(question); // Audio-Version: Frage vorlesen if (isAudioMode) { SpeakQuestion(question); } OnQuizStarted?.Invoke(); } // ============================================== // Antwort verarbeiten // ============================================== private void OnAnswerSelected(int index) { if (!isQuizActive || currentQuestion == null) return; // Timer stoppen if (timerCoroutine != null) { StopCoroutine(timerCoroutine); timerCoroutine = null; } bool isCorrect = index == currentQuestion.correct_index; int points = isCorrect ? pointsCorrect : pointsWrong; questionsAnswered++; if (isCorrect) questionsCorrect++; // Visuelles Feedback ShowAnswerFeedback(isCorrect, currentQuestion.correct_index); // Audio Feedback if (isAudioMode) { SpeakFeedback(isCorrect); } // Event ausloesen OnQuestionAnswered?.Invoke(isCorrect, points); // Quiz beenden (mit kurzer Verzoegerung fuer Feedback) StartCoroutine(EndQuizAfterDelay(isCorrect ? 0.5f : 1f)); } private IEnumerator EndQuizAfterDelay(float delay) { // Bei pausiertem Spiel: unscaled Zeit nutzen yield return new WaitForSecondsRealtime(delay); EndQuiz(); } private void EndQuiz() { isQuizActive = false; currentQuestion = null; // UI verstecken if (quickQuizPanel) quickQuizPanel.SetActive(false); if (pauseQuizPanel) pauseQuizPanel.SetActive(false); // Spiel fortsetzen (falls pausiert) Time.timeScale = 1; OnQuizEnded?.Invoke(); } // ============================================== // UI Helper // ============================================== private void DisplayQuestion(QuizQuestion question) { if (questionText) questionText.text = question.question_text; for (int i = 0; i < answerButtons.Length; i++) { if (i < question.options.Length) { answerButtons[i].gameObject.SetActive(true); var buttonText = answerButtons[i].GetComponentInChildren(); if (buttonText) buttonText.text = $"[{i + 1}] {question.options[i]}"; } else { answerButtons[i].gameObject.SetActive(false); } } } private void ShowAnswerFeedback(bool correct, int correctIndex) { // Richtige Antwort gruen markieren if (correctIndex >= 0 && correctIndex < answerButtons.Length) { var colors = answerButtons[correctIndex].colors; colors.normalColor = Color.green; answerButtons[correctIndex].colors = colors; } // TODO: Animation, Sound, Partikel } // ============================================== // Audio-Version // ============================================== private void SpeakQuestion(QuizQuestion question) { // TODO: Text-to-Speech Integration // Option 1: Voraufgenommene Audio-Dateien // Option 2: Web-TTS API // Option 3: Unity Speech Synthesis (platform-abhaengig) Debug.Log($"[TTS] {question.question_text}"); for (int i = 0; i < question.options.Length; i++) { Debug.Log($"[TTS] {i + 1} fuer {question.options[i]}"); } } private void SpeakFeedback(bool correct) { string message = correct ? "Richtig! Gut gemacht!" : "Nicht ganz. Versuch es nochmal!"; Debug.Log($"[TTS] {message}"); } // ============================================== // Statistik // ============================================== public int GetQuestionsAnswered() => questionsAnswered; public int GetQuestionsCorrect() => questionsCorrect; public float GetAccuracy() { if (questionsAnswered == 0) return 0; return (float)questionsCorrect / questionsAnswered; } public void ResetStatistics() { questionsAnswered = 0; questionsCorrect = 0; } private float GetCurrentDifficulty() { // TODO: Aus GameManager holen return 3; } } }