using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemySpawner : MonoBehaviour { [System.Serializable] public class EnemyTypeData { [Header("Enemy Prefabs")] public List prefabVariants = new List(); [Header("Spawn Selection")] [Min(0)] public int spawnChance = 10; public float danger = 0f; [Min(0)] public int enemyDifficulty = 1; } [Header("Enemy Types")] public List enemyTypes = new List(); [Header("Spawn Point")] public Transform spawnPoint; [Header("Difficulty")] public float raiseRate = 0.1f; public float difficultyRaise = 0f; public float difficulty = 0f; [Header("Initial Spawn")] public float startDelay = 5f; [Header("Failed Spawn Break")] public float minBreak = 3f; public float maxBreak = 7f; [Header("Group Spawn")] public float spawnDelay = 0.5f; public float minGroupBreak = 2f; public float maxGroupBreak = 5f; public int minGroup = 2; public int maxGroup = 5; private float tempDifficulty; private bool hasSpawnedGroupThisCycle = false; private bool pauseDifficultyGrowth = false; private void Start() { if (spawnPoint == null) { Debug.LogError( "EnemySpawner: Kein SpawnPoint zugewiesen!", this ); enabled = false; return; } if (!HasUsableEnemyType()) { Debug.LogError( "EnemySpawner: Keine gültigen Gegnertypen vorhanden!", this ); enabled = false; return; } StartCoroutine(SpawnLoop()); } private void Update() { // Freischaltschwelle wächst immer weiter. difficultyRaise += raiseRate * Time.deltaTime; // Budget wächst nur, wenn es nicht pausiert ist. if (!pauseDifficultyGrowth) { difficulty += difficultyRaise * Time.deltaTime; } Debug.Log("DifficultyRaise: " + difficultyRaise + " und Difficulty: " + difficulty); } private IEnumerator SpawnLoop() { // Einmalige Wartezeit beim Spielstart. yield return new WaitForSeconds( Mathf.Max(0f, startDelay) ); while (true) { // Neuer Zyklus beginnt. hasSpawnedGroupThisCycle = false; while (true) { List group; float resultingDifficulty; bool groupCanSpawn = TryBuildGroup( out group, out resultingDifficulty ); // Keine gültige Gruppe möglich: // Der aktuelle Zyklus endet. if (!groupCanSpawn) { pauseDifficultyGrowth = false; break; } // Nur zwischen Gruppen desselben Zyklus warten. if (hasSpawnedGroupThisCycle) { pauseDifficultyGrowth = true; yield return new WaitForSeconds( GetRandomDelay(minGroupBreak, maxGroupBreak) ); } // Budget während des tatsächlichen Gruppenspawns // ebenfalls einfrieren. pauseDifficultyGrowth = true; for (int i = 0; i < group.Count; i++) { SpawnEnemy(group[i]); if (i < group.Count - 1) { yield return new WaitForSeconds( Mathf.Max(0f, spawnDelay) ); } } difficulty = resultingDifficulty; // Ab jetzt darf eine weitere Gruppe in diesem // Zyklus erst nach einer GroupBreak spawnen. hasSpawnedGroupThisCycle = true; // Budget nach dem Gruppenspawn wieder freigeben. pauseDifficultyGrowth = false; } // Zyklus beendet: normale Pause, während der das // Budget weiterwachsen darf. pauseDifficultyGrowth = false; yield return new WaitForSeconds( GetRandomDelay(minBreak, maxBreak) ); } } private bool TryBuildGroup( out List group, out float resultingDifficulty) { group = new List(); resultingDifficulty = difficulty; // Nur freigeschaltete Gegnertypen berücksichtigen. // Die Budgetkosten werden hier noch NICHT gefiltert. List availableEnemies = GetAvailableEnemies(); if (availableEnemies.Count == 0) { return false; } int targetGroupSize = Random.Range( minGroup, maxGroup + 1 ); tempDifficulty = difficulty; for (int i = 0; i < targetGroupSize; i++) { // Auswahl ausschließlich anhand von spawnChance. EnemyTypeData selectedEnemy = ChooseWeightedEnemy(availableEnemies); if (selectedEnemy == null) { return false; } // Kosten des ausgewählten Gegners abziehen. tempDifficulty -= selectedEnemy.enemyDifficulty; if (tempDifficulty < 0f) { // D2: Die bisherigen Gegner reichen bereits // für die Mindestgruppengröße. if (group.Count >= minGroup) { // Die Kosten des abgelehnten Gegners // wieder hinzurechnen. resultingDifficulty = tempDifficulty + selectedEnemy.enemyDifficulty; return true; } // D3: Mindestgruppengröße nicht erreicht. // Nichts spawnen und Budget nicht verändern. return false; } // Der Gegner passt ins Budget und wird vorgemerkt. group.Add(selectedEnemy); } // D1: Die gewünschte Gruppengröße wurde erreicht. resultingDifficulty = tempDifficulty; return true; } private List GetAvailableEnemies() { List available = new List(); foreach (EnemyTypeData enemy in enemyTypes) { if (enemy == null) { continue; } if (enemy.danger > difficultyRaise) { continue; } if (enemy.spawnChance <= 0) { continue; } if (enemy.enemyDifficulty < 0) { continue; } if (enemy.prefabVariants == null || enemy.prefabVariants.Count == 0) { continue; } bool hasValidPrefab = false; foreach (GameObject prefab in enemy.prefabVariants) { if (prefab != null) { hasValidPrefab = true; break; } } if (hasValidPrefab) { available.Add(enemy); } } return available; } private EnemyTypeData ChooseWeightedEnemy( List availableEnemies) { int totalWeight = 0; foreach (EnemyTypeData enemy in availableEnemies) { totalWeight += enemy.spawnChance; } if (totalWeight <= 0) { return null; } int roll = Random.Range(1, totalWeight + 1); int cumulativeWeight = 0; foreach (EnemyTypeData enemy in availableEnemies) { cumulativeWeight += enemy.spawnChance; if (roll <= cumulativeWeight) { return enemy; } } return null; } private void SpawnEnemy(EnemyTypeData enemy) { List validPrefabs = new List(); foreach (GameObject prefab in enemy.prefabVariants) { if (prefab != null) { validPrefabs.Add(prefab); } } if (validPrefabs.Count == 0) { Debug.LogError( "EnemySpawner: Keine gültige Prefabvariante vorhanden!", this ); return; } // Zufällige Prefabvariante wählen. int prefabIndex = Random.Range(0, validPrefabs.Count); GameObject selectedPrefab = validPrefabs[prefabIndex]; GameObject spawnedEnemy = Instantiate( selectedPrefab, spawnPoint.position, spawnPoint.rotation ); // Das Sprite hat eine 50-prozentige Chance, // vertikal gespiegelt zu werden. SpriteRenderer spriteRenderer = spawnedEnemy.GetComponentInChildren(); if (spriteRenderer != null) { spriteRenderer.flipX = Random.value < 0.5f; } } private bool HasUsableEnemyType() { return GetAvailableEnemies().Count > 0 || HasAnyConfiguredEnemy(); } private bool HasAnyConfiguredEnemy() { foreach (EnemyTypeData enemy in enemyTypes) { if (enemy == null || enemy.spawnChance <= 0) { continue; } if (enemy.prefabVariants == null) { continue; } foreach (GameObject prefab in enemy.prefabVariants) { if (prefab != null) { return true; } } } return false; } private float GetRandomDelay(float min, float max) { min = Mathf.Max(0f, min); max = Mathf.Max(0f, max); if (max < min) { float temp = min; min = max; max = temp; } return Random.Range(min, max); } private void OnValidate() { raiseRate = Mathf.Max(0f, raiseRate); startDelay = Mathf.Max(0f, startDelay); spawnDelay = Mathf.Max(0f, spawnDelay); minGroup = Mathf.Max(1, minGroup); maxGroup = Mathf.Max(minGroup, maxGroup); } }