This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/breakpilot-drive/UnityScripts/Player/PlayerController.cs
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

374 lines
11 KiB
C#

// ==============================================
// 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<string> OnItemCollected;
// Components
private Rigidbody rb;
private Animator animator;
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
rb = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
}
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<VisualTrigger>();
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<Renderer>();
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();
}
}
}