Gegner Spawnlogik und Schwierigkeitserhöhung mit Normalo-Gegner
This commit is contained in:
@@ -4,7 +4,7 @@ public class Enemy : MonoBehaviour, IMovementDirection
|
||||
{
|
||||
[Header("Movement")]
|
||||
[SerializeField]
|
||||
private float moveSpeed = 2f;
|
||||
private float moveSpeed = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
private float targetReachedDistance = 0.1f;
|
||||
|
||||
+383
-31
@@ -1,34 +1,57 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class EnemySpawner : MonoBehaviour
|
||||
{
|
||||
[Header("Enemy")]
|
||||
[SerializeField]
|
||||
private GameObject enemyPrefab;
|
||||
[System.Serializable]
|
||||
public class EnemyTypeData
|
||||
{
|
||||
[Header("Enemy Prefabs")]
|
||||
public List<GameObject> prefabVariants = new List<GameObject>();
|
||||
|
||||
[Header("Spawn")]
|
||||
[SerializeField]
|
||||
private Transform spawnPoint;
|
||||
[Header("Spawn Selection")]
|
||||
[Min(0)]
|
||||
public int spawnChance = 10;
|
||||
|
||||
[SerializeField]
|
||||
private float minSpawnDelay = 0.5f;
|
||||
public float danger = 0f;
|
||||
|
||||
[SerializeField]
|
||||
private float maxSpawnDelay = 2f;
|
||||
[Min(0)]
|
||||
public int enemyDifficulty = 1;
|
||||
}
|
||||
|
||||
[Header("Enemy Types")]
|
||||
public List<EnemyTypeData> enemyTypes = new List<EnemyTypeData>();
|
||||
|
||||
[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 (enemyPrefab == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"EnemySpawner: Kein Enemy-Prefab zugewiesen!",
|
||||
this
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
@@ -36,33 +59,362 @@ public class EnemySpawner : MonoBehaviour
|
||||
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)
|
||||
{
|
||||
float delay = UnityEngine.Random.Range(
|
||||
minSpawnDelay,
|
||||
maxSpawnDelay
|
||||
// Neuer Zyklus beginnt.
|
||||
hasSpawnedGroupThisCycle = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
List<EnemyTypeData> 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)
|
||||
);
|
||||
|
||||
yield return new WaitForSeconds(delay);
|
||||
|
||||
SpawnEnemy();
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnEnemy()
|
||||
private bool TryBuildGroup(
|
||||
out List<EnemyTypeData> group,
|
||||
out float resultingDifficulty)
|
||||
{
|
||||
Instantiate(
|
||||
enemyPrefab,
|
||||
group = new List<EnemyTypeData>();
|
||||
resultingDifficulty = difficulty;
|
||||
|
||||
// Nur freigeschaltete Gegnertypen berücksichtigen.
|
||||
// Die Budgetkosten werden hier noch NICHT gefiltert.
|
||||
List<EnemyTypeData> 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<EnemyTypeData> GetAvailableEnemies()
|
||||
{
|
||||
List<EnemyTypeData> available =
|
||||
new List<EnemyTypeData>();
|
||||
|
||||
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<EnemyTypeData> 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<GameObject> validPrefabs = new List<GameObject>();
|
||||
|
||||
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<SpriteRenderer>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class OLD_EnemySpawner : MonoBehaviour
|
||||
{
|
||||
[Header("Enemy")]
|
||||
[SerializeField]
|
||||
private GameObject enemyPrefab;
|
||||
|
||||
[Header("Spawn")]
|
||||
[SerializeField]
|
||||
private Transform spawnPoint;
|
||||
|
||||
[SerializeField]
|
||||
private float minSpawnDelay = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
private float maxSpawnDelay = 2f;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (enemyPrefab == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"EnemySpawner: Kein Enemy-Prefab zugewiesen!",
|
||||
this
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"EnemySpawner: Kein SpawnPoint zugewiesen!",
|
||||
this
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(SpawnLoop());
|
||||
}
|
||||
|
||||
private IEnumerator SpawnLoop()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
float delay = UnityEngine.Random.Range(
|
||||
minSpawnDelay,
|
||||
maxSpawnDelay
|
||||
);
|
||||
|
||||
yield return new WaitForSeconds(delay);
|
||||
|
||||
SpawnEnemy();
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnEnemy()
|
||||
{
|
||||
Instantiate(
|
||||
enemyPrefab,
|
||||
spawnPoint.position,
|
||||
spawnPoint.rotation
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8b6d72d4e53e5d4cb31ba8ec6903a94
|
||||
Reference in New Issue
Block a user