Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
248
breakpilot-drive/UnityScripts/Track/ObstacleSpawner.cs
Normal file
248
breakpilot-drive/UnityScripts/Track/ObstacleSpawner.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
// ==============================================
|
||||
// ObstacleSpawner.cs - Hindernis-Generator
|
||||
// ==============================================
|
||||
// Spawnt Hindernisse, Items und Quiz-Trigger
|
||||
// basierend auf Schwierigkeitsgrad.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace BreakpilotDrive
|
||||
{
|
||||
public class ObstacleSpawner : MonoBehaviour
|
||||
{
|
||||
public static ObstacleSpawner Instance { get; private set; }
|
||||
|
||||
[Header("Hindernis-Prefabs")]
|
||||
[SerializeField] private GameObject[] obstaclePrefabs; // Verschiedene Hindernisse
|
||||
[SerializeField] private GameObject[] largObstaclePrefabs; // Grosse Hindernisse (2 Spuren)
|
||||
|
||||
[Header("Item-Prefabs")]
|
||||
[SerializeField] private GameObject coinPrefab;
|
||||
[SerializeField] private GameObject starPrefab;
|
||||
[SerializeField] private GameObject shieldPrefab;
|
||||
|
||||
[Header("Quiz-Trigger-Prefabs")]
|
||||
[SerializeField] private GameObject bridgePrefab;
|
||||
[SerializeField] private GameObject treePrefab;
|
||||
[SerializeField] private GameObject housePrefab;
|
||||
|
||||
[Header("Spawn-Einstellungen")]
|
||||
[SerializeField] private float laneDistance = 3f;
|
||||
[SerializeField] private float minObstacleSpacing = 10f;
|
||||
[SerializeField] private float maxObstacleSpacing = 30f;
|
||||
|
||||
[Header("Wahrscheinlichkeiten (0-1)")]
|
||||
[SerializeField] private float obstacleChance = 0.6f;
|
||||
[SerializeField] private float coinChance = 0.3f;
|
||||
[SerializeField] private float starChance = 0.05f;
|
||||
[SerializeField] private float shieldChance = 0.02f;
|
||||
[SerializeField] private float quizTriggerChance = 0.1f;
|
||||
|
||||
// Interner Zustand
|
||||
private float lastObstacleZ = 0f;
|
||||
private int quizTriggerCount = 0;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Wahrscheinlichkeiten von Difficulty laden
|
||||
UpdateFromDifficulty();
|
||||
}
|
||||
|
||||
private void UpdateFromDifficulty()
|
||||
{
|
||||
if (GameManager.Instance?.DifficultySettings != null)
|
||||
{
|
||||
var settings = GameManager.Instance.DifficultySettings;
|
||||
obstacleChance = settings.obstacle_frequency;
|
||||
// Power-Up Chance beeinflusst Items
|
||||
float powerUpChance = settings.power_up_chance;
|
||||
coinChance = powerUpChance * 0.8f;
|
||||
starChance = powerUpChance * 0.15f;
|
||||
shieldChance = powerUpChance * 0.05f;
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Spawning auf Segment
|
||||
// ==============================================
|
||||
public void SpawnOnSegment(GameObject segment, float segmentStartZ)
|
||||
{
|
||||
float segmentLength = 50f; // Sollte mit TrackGenerator uebereinstimmen
|
||||
float currentZ = segmentStartZ + minObstacleSpacing;
|
||||
|
||||
while (currentZ < segmentStartZ + segmentLength - minObstacleSpacing)
|
||||
{
|
||||
// Zufaelliger Abstand zum naechsten Objekt
|
||||
float spacing = Random.Range(minObstacleSpacing, maxObstacleSpacing);
|
||||
|
||||
// Was spawnen?
|
||||
SpawnAtPosition(segment.transform, currentZ);
|
||||
|
||||
currentZ += spacing;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnAtPosition(Transform parent, float zPosition)
|
||||
{
|
||||
float roll = Random.value;
|
||||
|
||||
// Quiz-Trigger (selten, aber wichtig)
|
||||
if (roll < quizTriggerChance && quizTriggerCount < 3)
|
||||
{
|
||||
SpawnQuizTrigger(parent, zPosition);
|
||||
quizTriggerCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Hindernis
|
||||
roll = Random.value;
|
||||
if (roll < obstacleChance)
|
||||
{
|
||||
SpawnObstacle(parent, zPosition);
|
||||
}
|
||||
|
||||
// Items (koennen neben Hindernissen sein)
|
||||
roll = Random.value;
|
||||
if (roll < coinChance)
|
||||
{
|
||||
SpawnCoinRow(parent, zPosition);
|
||||
}
|
||||
else if (roll < coinChance + starChance)
|
||||
{
|
||||
SpawnItem(starPrefab, parent, zPosition, GetRandomLane());
|
||||
}
|
||||
else if (roll < coinChance + starChance + shieldChance)
|
||||
{
|
||||
SpawnItem(shieldPrefab, parent, zPosition, GetRandomLane());
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Hindernisse
|
||||
// ==============================================
|
||||
private void SpawnObstacle(Transform parent, float zPosition)
|
||||
{
|
||||
if (obstaclePrefabs == null || obstaclePrefabs.Length == 0) return;
|
||||
|
||||
// Zufaelliges Hindernis und Spur waehlen
|
||||
int prefabIndex = Random.Range(0, obstaclePrefabs.Length);
|
||||
int lane = GetRandomLane();
|
||||
|
||||
// Manchmal grosses Hindernis (blockiert 2 Spuren)
|
||||
if (largObstaclePrefabs != null && largObstaclePrefabs.Length > 0 && Random.value < 0.2f)
|
||||
{
|
||||
prefabIndex = Random.Range(0, largObstaclePrefabs.Length);
|
||||
SpawnObstacleAtLane(largObstaclePrefabs[prefabIndex], parent, zPosition, lane);
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnObstacleAtLane(obstaclePrefabs[prefabIndex], parent, zPosition, lane);
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnObstacleAtLane(GameObject prefab, Transform parent, float zPosition, int lane)
|
||||
{
|
||||
float xPos = (lane - 1) * laneDistance;
|
||||
Vector3 position = new Vector3(xPos, 0, zPosition);
|
||||
|
||||
GameObject obstacle = Instantiate(prefab, parent);
|
||||
obstacle.transform.localPosition = position;
|
||||
obstacle.tag = "Obstacle";
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Items
|
||||
// ==============================================
|
||||
private void SpawnItem(GameObject prefab, Transform parent, float zPosition, int lane)
|
||||
{
|
||||
if (prefab == null) return;
|
||||
|
||||
float xPos = (lane - 1) * laneDistance;
|
||||
Vector3 position = new Vector3(xPos, 0.5f, zPosition); // Leicht ueber dem Boden
|
||||
|
||||
GameObject item = Instantiate(prefab, parent);
|
||||
item.transform.localPosition = position;
|
||||
}
|
||||
|
||||
private void SpawnCoinRow(Transform parent, float zPosition)
|
||||
{
|
||||
if (coinPrefab == null) return;
|
||||
|
||||
int lane = GetRandomLane();
|
||||
int coinCount = Random.Range(3, 6);
|
||||
|
||||
for (int i = 0; i < coinCount; i++)
|
||||
{
|
||||
SpawnItem(coinPrefab, parent, zPosition + (i * 2f), lane);
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Quiz-Trigger
|
||||
// ==============================================
|
||||
private void SpawnQuizTrigger(Transform parent, float zPosition)
|
||||
{
|
||||
GameObject prefab = GetRandomTriggerPrefab();
|
||||
if (prefab == null) return;
|
||||
|
||||
// Trigger sind meist in der Mitte oder ueber der Strasse
|
||||
Vector3 position = new Vector3(0, 0, zPosition);
|
||||
|
||||
GameObject trigger = Instantiate(prefab, parent);
|
||||
trigger.transform.localPosition = position;
|
||||
trigger.tag = "QuizTrigger";
|
||||
|
||||
// VisualTrigger-Komponente hinzufuegen falls nicht vorhanden
|
||||
if (trigger.GetComponent<VisualTrigger>() == null)
|
||||
{
|
||||
VisualTrigger vt = trigger.AddComponent<VisualTrigger>();
|
||||
vt.TriggerType = GetTriggerTypeFromPrefab(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject GetRandomTriggerPrefab()
|
||||
{
|
||||
GameObject[] triggers = new GameObject[] { bridgePrefab, treePrefab, housePrefab };
|
||||
var validTriggers = System.Array.FindAll(triggers, t => t != null);
|
||||
|
||||
if (validTriggers.Length == 0) return null;
|
||||
|
||||
return validTriggers[Random.Range(0, validTriggers.Length)];
|
||||
}
|
||||
|
||||
private string GetTriggerTypeFromPrefab(GameObject prefab)
|
||||
{
|
||||
if (prefab == bridgePrefab) return "bridge";
|
||||
if (prefab == treePrefab) return "tree";
|
||||
if (prefab == housePrefab) return "house";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Helper
|
||||
// ==============================================
|
||||
private int GetRandomLane()
|
||||
{
|
||||
return Random.Range(0, 3); // 0, 1, oder 2
|
||||
}
|
||||
|
||||
public void ResetSpawner()
|
||||
{
|
||||
lastObstacleZ = 0f;
|
||||
quizTriggerCount = 0;
|
||||
UpdateFromDifficulty();
|
||||
}
|
||||
}
|
||||
}
|
||||
233
breakpilot-drive/UnityScripts/Track/TrackGenerator.cs
Normal file
233
breakpilot-drive/UnityScripts/Track/TrackGenerator.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
// ==============================================
|
||||
// TrackGenerator.cs - Endlose Streckengenerierung
|
||||
// ==============================================
|
||||
// Erzeugt endlose Streckenabschnitte durch
|
||||
// Object-Pooling und Recycling.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BreakpilotDrive
|
||||
{
|
||||
public class TrackGenerator : MonoBehaviour
|
||||
{
|
||||
public static TrackGenerator Instance { get; private set; }
|
||||
|
||||
[Header("Track Prefabs")]
|
||||
[SerializeField] private GameObject[] trackSegmentPrefabs; // Verschiedene Streckenabschnitte
|
||||
[SerializeField] private GameObject startSegmentPrefab; // Start-Segment
|
||||
|
||||
[Header("Generierung")]
|
||||
[SerializeField] private float segmentLength = 50f; // Laenge eines Segments
|
||||
[SerializeField] private int visibleSegments = 5; // Anzahl sichtbarer Segmente
|
||||
[SerializeField] private float despawnDistance = -20f; // Wann Segment recycelt wird
|
||||
|
||||
[Header("Bewegung")]
|
||||
[SerializeField] private float baseSpeed = 10f; // Basis-Geschwindigkeit
|
||||
private float currentSpeed;
|
||||
|
||||
// Object Pool
|
||||
private List<GameObject> activeSegments = new List<GameObject>();
|
||||
private Queue<GameObject> segmentPool = new Queue<GameObject>();
|
||||
|
||||
// Position
|
||||
private float nextSpawnZ = 0f;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
currentSpeed = baseSpeed;
|
||||
|
||||
// Geschwindigkeit von Difficulty laden
|
||||
if (GameManager.Instance?.DifficultySettings != null)
|
||||
{
|
||||
currentSpeed = GameManager.Instance.DifficultySettings.lane_speed;
|
||||
}
|
||||
|
||||
// Initiale Segmente erstellen
|
||||
InitializeTrack();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (GameManager.Instance?.CurrentState != GameState.Playing)
|
||||
return;
|
||||
|
||||
// Segmente bewegen
|
||||
MoveSegments();
|
||||
|
||||
// Alte Segmente recyceln
|
||||
RecycleSegments();
|
||||
|
||||
// Neue Segmente spawnen
|
||||
SpawnSegmentsIfNeeded();
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Initialisierung
|
||||
// ==============================================
|
||||
private void InitializeTrack()
|
||||
{
|
||||
nextSpawnZ = 0f;
|
||||
|
||||
// Start-Segment
|
||||
if (startSegmentPrefab != null)
|
||||
{
|
||||
SpawnSegment(startSegmentPrefab);
|
||||
}
|
||||
|
||||
// Weitere Segmente fuer sichtbaren Bereich
|
||||
for (int i = 0; i < visibleSegments; i++)
|
||||
{
|
||||
SpawnRandomSegment();
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Segment-Spawning
|
||||
// ==============================================
|
||||
private void SpawnRandomSegment()
|
||||
{
|
||||
if (trackSegmentPrefabs == null || trackSegmentPrefabs.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("Keine Track-Segment-Prefabs zugewiesen!");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = Random.Range(0, trackSegmentPrefabs.Length);
|
||||
SpawnSegment(trackSegmentPrefabs[index]);
|
||||
}
|
||||
|
||||
private void SpawnSegment(GameObject prefab)
|
||||
{
|
||||
GameObject segment;
|
||||
|
||||
// Aus Pool holen oder neu erstellen
|
||||
if (segmentPool.Count > 0)
|
||||
{
|
||||
segment = segmentPool.Dequeue();
|
||||
segment.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
segment = Instantiate(prefab, transform);
|
||||
}
|
||||
|
||||
// Position setzen
|
||||
segment.transform.position = new Vector3(0, 0, nextSpawnZ);
|
||||
nextSpawnZ += segmentLength;
|
||||
|
||||
activeSegments.Add(segment);
|
||||
|
||||
// Hindernisse und Items auf Segment spawnen
|
||||
ObstacleSpawner.Instance?.SpawnOnSegment(segment, nextSpawnZ - segmentLength);
|
||||
}
|
||||
|
||||
private void SpawnSegmentsIfNeeded()
|
||||
{
|
||||
// Pruefe ob wir mehr Segmente brauchen
|
||||
float playerZ = 0; // Spieler ist bei Z=0, Welt bewegt sich
|
||||
float maxVisibleZ = playerZ + (visibleSegments * segmentLength);
|
||||
|
||||
while (nextSpawnZ < maxVisibleZ)
|
||||
{
|
||||
SpawnRandomSegment();
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Bewegung
|
||||
// ==============================================
|
||||
private void MoveSegments()
|
||||
{
|
||||
Vector3 movement = Vector3.back * currentSpeed * Time.deltaTime;
|
||||
|
||||
foreach (var segment in activeSegments)
|
||||
{
|
||||
segment.transform.position += movement;
|
||||
}
|
||||
|
||||
// Auch nextSpawnZ anpassen
|
||||
nextSpawnZ -= currentSpeed * Time.deltaTime;
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Recycling
|
||||
// ==============================================
|
||||
private void RecycleSegments()
|
||||
{
|
||||
for (int i = activeSegments.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (activeSegments[i].transform.position.z < despawnDistance)
|
||||
{
|
||||
RecycleSegment(activeSegments[i]);
|
||||
activeSegments.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RecycleSegment(GameObject segment)
|
||||
{
|
||||
// Kinder-Objekte (Hindernisse, Items) entfernen
|
||||
foreach (Transform child in segment.transform)
|
||||
{
|
||||
if (child.CompareTag("Obstacle") || child.CompareTag("Coin") ||
|
||||
child.CompareTag("Star") || child.CompareTag("QuizTrigger"))
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Segment deaktivieren und zurueck in Pool
|
||||
segment.SetActive(false);
|
||||
segmentPool.Enqueue(segment);
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Geschwindigkeit
|
||||
// ==============================================
|
||||
public void SetSpeed(float speed)
|
||||
{
|
||||
currentSpeed = speed;
|
||||
}
|
||||
|
||||
public float GetSpeed()
|
||||
{
|
||||
return currentSpeed;
|
||||
}
|
||||
|
||||
public void IncreaseSpeed(float amount)
|
||||
{
|
||||
currentSpeed += amount;
|
||||
}
|
||||
|
||||
// ==============================================
|
||||
// Reset
|
||||
// ==============================================
|
||||
public void ResetTrack()
|
||||
{
|
||||
// Alle aktiven Segmente recyceln
|
||||
foreach (var segment in activeSegments)
|
||||
{
|
||||
RecycleSegment(segment);
|
||||
}
|
||||
activeSegments.Clear();
|
||||
|
||||
// Neu initialisieren
|
||||
nextSpawnZ = 0f;
|
||||
currentSpeed = baseSpeed;
|
||||
InitializeTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
breakpilot-drive/UnityScripts/Track/VisualTrigger.cs
Normal file
57
breakpilot-drive/UnityScripts/Track/VisualTrigger.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// ==============================================
|
||||
// VisualTrigger.cs - Quiz-Trigger Objekt
|
||||
// ==============================================
|
||||
// Loest Quick-Quiz aus wenn Spieler durchfaehrt.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace BreakpilotDrive
|
||||
{
|
||||
public class VisualTrigger : MonoBehaviour
|
||||
{
|
||||
[Header("Trigger-Konfiguration")]
|
||||
[SerializeField] private string triggerType = "bridge"; // "bridge", "tree", "house"
|
||||
|
||||
public string TriggerType
|
||||
{
|
||||
get => triggerType;
|
||||
set => triggerType = value;
|
||||
}
|
||||
|
||||
private bool hasTriggered = false;
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (hasTriggered) return;
|
||||
|
||||
if (other.CompareTag("Player"))
|
||||
{
|
||||
hasTriggered = true;
|
||||
|
||||
// Quick-Quiz ausloesen
|
||||
if (QuizManager.Instance != null)
|
||||
{
|
||||
QuizManager.Instance.ShowQuickQuestion(triggerType);
|
||||
}
|
||||
|
||||
Debug.Log($"Quiz-Trigger ausgeloest: {triggerType}");
|
||||
}
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
// Reset fuer naechsten Durchlauf (falls Objekt recycelt wird)
|
||||
if (other.CompareTag("Player"))
|
||||
{
|
||||
// Optional: Trigger nach kurzer Zeit wieder aktivieren
|
||||
// hasTriggered = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Wird aufgerufen wenn Objekt recycelt wird
|
||||
void OnDisable()
|
||||
{
|
||||
hasTriggered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user