feat: BreakPilot PWA - Full codebase (clean push without large binaries)
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed

All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
This commit is contained in:
BreakPilot Dev
2026-02-11 13:25:58 +01:00
commit 19855efacc
2512 changed files with 933814 additions and 0 deletions

View File

@@ -0,0 +1,373 @@
// ==============================================
// 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();
}
}
}