// ============================================== // 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() == null) { VisualTrigger vt = trigger.AddComponent(); 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(); } } }