// ============================================== // PlayerController.cs - Spieler-Steuerung // ============================================== // 3-Spur-System mit Tastatur und Touch-Eingabe. // Kollisionserkennung fuer Hindernisse und Items. using System; using UnityEngine; namespace BreakpilotDrive { public class PlayerController : MonoBehaviour { public static PlayerController Instance { get; private set; } [Header("Bewegung")] [SerializeField] private float laneDistance = 3f; // Abstand zwischen Spuren [SerializeField] private float laneSwitchSpeed = 10f; // Geschwindigkeit beim Spurwechsel [SerializeField] private float forwardSpeed = 10f; // Vorwaertsgeschwindigkeit [Header("Spuren")] [SerializeField] private int currentLane = 1; // 0=Links, 1=Mitte, 2=Rechts private int targetLane = 1; [Header("Touch-Steuerung")] [SerializeField] private float swipeThreshold = 50f; // Pixel fuer Swipe-Erkennung private Vector2 touchStartPos; private bool isSwiping = false; [Header("Effekte")] [SerializeField] private ParticleSystem crashEffect; [SerializeField] private ParticleSystem collectEffect; // Zustand private bool isAlive = true; private bool isInvincible = false; private float invincibleTimer = 0f; // Events public event Action OnCrash; public event Action OnItemCollected; // Components private Rigidbody rb; private Animator animator; void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } rb = GetComponent(); animator = GetComponent(); } void Start() { // Startposition (mittlere Spur) currentLane = 1; targetLane = 1; UpdateTargetPosition(); // Geschwindigkeit von Difficulty laden if (GameManager.Instance?.DifficultySettings != null) { forwardSpeed = GameManager.Instance.DifficultySettings.lane_speed; } } void Update() { if (!isAlive || GameManager.Instance?.CurrentState != GameState.Playing) return; // Eingabe verarbeiten HandleInput(); // Zur Zielspur bewegen MoveToTargetLane(); // Vorwaerts bewegen MoveForward(); // Unverwundbarkeit Timer if (isInvincible) { invincibleTimer -= Time.deltaTime; if (invincibleTimer <= 0) { isInvincible = false; // Blink-Effekt beenden SetVisibility(true); } } // Distanz zum GameManager melden GameManager.Instance?.AddDistance(forwardSpeed * Time.deltaTime); } // ============================================== // Eingabe-Verarbeitung // ============================================== private void HandleInput() { // Tastatur-Eingabe if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow)) { MoveLane(-1); } else if (Input.GetKeyDown(KeyCode.D) || Input.GetKeyDown(KeyCode.RightArrow)) { MoveLane(1); } // Direkte Spurwahl mit Zahlen (auch fuer Quiz) if (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1)) { SetLane(0); } else if (Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2)) { SetLane(1); } else if (Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3)) { SetLane(2); } // Touch-Eingabe HandleTouchInput(); } private void HandleTouchInput() { if (Input.touchCount > 0) { Touch touch = Input.GetTouch(0); switch (touch.phase) { case TouchPhase.Began: touchStartPos = touch.position; isSwiping = true; break; case TouchPhase.Moved: case TouchPhase.Ended: if (isSwiping) { Vector2 swipeDelta = touch.position - touchStartPos; if (Mathf.Abs(swipeDelta.x) > swipeThreshold) { if (swipeDelta.x > 0) { MoveLane(1); // Rechts } else { MoveLane(-1); // Links } isSwiping = false; } } break; } } // Maus-Simulation (fuer Editor-Tests) if (Input.GetMouseButtonDown(0)) { touchStartPos = Input.mousePosition; isSwiping = true; } else if (Input.GetMouseButtonUp(0) && isSwiping) { Vector2 swipeDelta = (Vector2)Input.mousePosition - touchStartPos; if (Mathf.Abs(swipeDelta.x) > swipeThreshold) { MoveLane(swipeDelta.x > 0 ? 1 : -1); } isSwiping = false; } } // ============================================== // Spur-Bewegung // ============================================== private void MoveLane(int direction) { targetLane = Mathf.Clamp(targetLane + direction, 0, 2); } private void SetLane(int lane) { targetLane = Mathf.Clamp(lane, 0, 2); } private void MoveToTargetLane() { float targetX = (targetLane - 1) * laneDistance; Vector3 targetPos = new Vector3(targetX, transform.position.y, transform.position.z); transform.position = Vector3.Lerp( transform.position, targetPos, laneSwitchSpeed * Time.deltaTime ); // Aktuelle Spur aktualisieren currentLane = targetLane; } private void MoveForward() { // Spieler bleibt stationaer, Welt bewegt sich // Alternativ: transform.Translate(Vector3.forward * forwardSpeed * Time.deltaTime); } private void UpdateTargetPosition() { float targetX = (targetLane - 1) * laneDistance; transform.position = new Vector3(targetX, transform.position.y, transform.position.z); } // ============================================== // Kollisionen // ============================================== void OnTriggerEnter(Collider other) { if (!isAlive) return; // Hindernis if (other.CompareTag("Obstacle")) { if (!isInvincible) { Crash(); } } // Sammelbare Items else if (other.CompareTag("Coin")) { CollectItem("coin", 100); Destroy(other.gameObject); } else if (other.CompareTag("Star")) { CollectItem("star", 500); Destroy(other.gameObject); } else if (other.CompareTag("Shield")) { CollectItem("shield", 0); ActivateShield(5f); Destroy(other.gameObject); } // Quiz-Trigger else if (other.CompareTag("QuizTrigger")) { VisualTrigger trigger = other.GetComponent(); if (trigger != null) { QuizManager.Instance?.ShowQuickQuestion(trigger.TriggerType); } } } // ============================================== // Crash-Handling // ============================================== private void Crash() { // Effekt abspielen if (crashEffect != null) { crashEffect.Play(); } // Animation if (animator != null) { animator.SetTrigger("Crash"); } // Leben verlieren GameManager.Instance?.LoseLife(); // Kurze Unverwundbarkeit isInvincible = true; invincibleTimer = 2f; StartCoroutine(BlinkEffect()); OnCrash?.Invoke(); } private System.Collections.IEnumerator BlinkEffect() { while (isInvincible) { SetVisibility(false); yield return new WaitForSeconds(0.1f); SetVisibility(true); yield return new WaitForSeconds(0.1f); } } private void SetVisibility(bool visible) { Renderer[] renderers = GetComponentsInChildren(); foreach (var r in renderers) { r.enabled = visible; } } // ============================================== // Item-Collection // ============================================== private void CollectItem(string itemType, int points) { // Effekt abspielen if (collectEffect != null) { collectEffect.Play(); } // Punkte hinzufuegen if (points > 0) { GameManager.Instance?.AddScore(points); } OnItemCollected?.Invoke(itemType); } private void ActivateShield(float duration) { isInvincible = true; invincibleTimer = duration; // TODO: Schild-Visualisierung } // ============================================== // Oeffentliche Methoden // ============================================== public void SetSpeed(float speed) { forwardSpeed = speed; } public int GetCurrentLane() { return currentLane; } public void Reset() { isAlive = true; isInvincible = false; currentLane = 1; targetLane = 1; UpdateTargetPosition(); } } }