Dateien sortiert, Hintergrund angepasst, Platzierungslogik und Basis für Combat angelegt. Gewehr schießt und Gegner nehmen Schaden

This commit is contained in:
PBM2H25BSH committed 2026-11-05 16:41:05 +01:00
1 parent 02b9ddb226
commit e8c81646c4
59 files changed
+2616 -179

No files matched your search

+108
View File
@@ -0,0 +1,108 @@
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
public class Bullet : MonoBehaviour
{
[Header("Runtime Values")]
private Vector2 direction;
private float speed;
private float damage;
private float maxDistance;
private float distanceTravelled;
private bool initialized;
// Speichert alle Gegner, die diese Kugel bereits getroffen hat.
private HashSet<Enemy> hitEnemies = new HashSet<Enemy>();
private Rigidbody2D rb;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
rb.gravityScale = 0f;
rb.bodyType = RigidbodyType2D.Kinematic;
rb.collisionDetectionMode =
CollisionDetectionMode2D.Continuous;
}
public void Initialize(
Vector2 shootDirection,
float bulletSpeed,
float bulletDamage,
float range
)
{
direction = shootDirection.normalized;
speed = bulletSpeed;
damage = bulletDamage;
maxDistance = range;
// Sprite zeigt standardmäßig mit seiner lokalen Y-Achse nach oben.
float angle =
Mathf.Atan2(direction.y, direction.x)
* Mathf.Rad2Deg - 90f;
transform.rotation =
Quaternion.Euler(0f, 0f, angle);
initialized = true;
}
private void FixedUpdate()
{
if (!initialized)
return;
float remainingDistance =
maxDistance - distanceTravelled;
if (remainingDistance <= 0f)
{
Destroy(gameObject);
return;
}
float movementDistance = Mathf.Min(
speed * Time.fixedDeltaTime,
remainingDistance
);
Vector2 movement =
direction * movementDistance;
rb.MovePosition(rb.position + movement);
distanceTravelled += movementDistance;
if (distanceTravelled >= maxDistance)
{
Destroy(gameObject);
}
}
private void OnTriggerEnter2D(Collider2D other)
{
Enemy enemy =
other.GetComponentInParent<Enemy>();
if (enemy == null)
return;
// Bereits getroffenen Gegner ignorieren.
if (!hitEnemies.Add(enemy))
return;
// Keine weiteren Treffer auf tote Gegner.
if (enemy.IsDead)
return;
enemy.TakeDamage(damage);
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4e19c6e90338ca14d8f1aa88ff9b01fc
@@ -0,0 +1,92 @@
using UnityEngine;
public class CharacterRotation : MonoBehaviour
{
[Header("Visual")]
[SerializeField]
private Transform visual;
[Header("Rotation")]
[SerializeField]
private float rotationSpeed = 540f;
[Tooltip("In welche lokale Richtung zeigt die Grafik standardm??ig?")]
[SerializeField]
private SpriteForwardDirection spriteForward =
SpriteForwardDirection.Up;
private IMovementDirection movement;
private enum SpriteForwardDirection
{
Up,
Right
}
private void Awake()
{
movement =
GetComponent<IMovementDirection>();
if (movement == null)
{
Debug.LogError(
"CharacterRotation ben?tigt eine Komponente, " +
"die IMovementDirection implementiert.",
this
);
}
if (visual == null)
{
Debug.LogError(
"CharacterRotation: Kein Visual zugewiesen.",
this
);
}
}
private void Update()
{
if (movement == null || visual == null)
return;
Vector2 direction =
movement.MovementDirection;
// Keine Bewegung -> aktuelle Rotation behalten.
if (direction.sqrMagnitude < 0.001f)
return;
RotateTowards(direction);
}
private void RotateTowards(Vector2 direction)
{
float targetAngle =
Mathf.Atan2(
direction.y,
direction.x
) * Mathf.Rad2Deg;
if (spriteForward ==
SpriteForwardDirection.Up)
{
targetAngle -= 90f;
}
Quaternion targetRotation =
Quaternion.Euler(
0f,
0f,
targetAngle
);
visual.rotation =
Quaternion.RotateTowards(
visual.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bea87a19f3a99294c9d065a24307484c
+195
View File
@@ -0,0 +1,195 @@
using UnityEngine;
public class Enemy : MonoBehaviour, IMovementDirection
{
[Header("Movement")]
[SerializeField]
private float moveSpeed = 0.5f;
[SerializeField]
private float targetReachedDistance = 0.1f;
private EnemyTargetManager targetManager;
private Transform currentTarget;
// Wird von CharacterRotation verwendet.
public Vector2 MovementDirection { get; private set; }
[Header("Health")]
[SerializeField]
private float maxHealth = 100f;
private float currentHealth;
public float CurrentHealth => currentHealth;
public bool IsDead { get; private set; }
private void Awake()
{
currentHealth = maxHealth;
targetManager =
FindFirstObjectByType<EnemyTargetManager>();
if (targetManager == null)
{
Debug.LogError(
"Enemy: Kein EnemyTargetManager gefunden!",
this
);
}
}
private void OnEnable()
{
if (targetManager != null)
{
targetManager.OnTargetsChanged +=
SelectClosestTarget;
}
}
private void OnDisable()
{
if (targetManager != null)
{
targetManager.OnTargetsChanged -=
SelectClosestTarget;
}
}
private void Start()
{
if (targetManager == null)
return;
SelectClosestTarget();
}
private void Update()
{
if (targetManager == null)
return;
// Kein aktuelles Ziel vorhanden.
if (currentTarget == null)
{
MovementDirection = Vector2.zero;
SelectClosestTarget();
return;
}
// Das aktuelle Ziel wurde deaktiviert.
if (!currentTarget.gameObject.activeInHierarchy)
{
SelectClosestTarget();
return;
}
MoveTowardsTarget();
// Ziel erreicht.
if (HasReachedTarget())
{
SelectClosestTarget();
}
}
// BEWEGUNGSLOGIK
//
private void SelectClosestTarget()
{
if (targetManager == null)
return;
currentTarget =
targetManager.GetClosestActiveTarget(
transform.position
);
if (currentTarget == null)
{
MovementDirection = Vector2.zero;
}
}
private void MoveTowardsTarget()
{
if (currentTarget == null)
return;
Vector3 direction =
currentTarget.position - transform.position;
MovementDirection = new Vector2(
direction.x,
direction.y
).normalized;
transform.position = Vector3.MoveTowards(
transform.position,
currentTarget.position,
moveSpeed * Time.deltaTime
);
}
private bool HasReachedTarget()
{
if (currentTarget == null)
return false;
return Vector3.Distance(
transform.position,
currentTarget.position
) <= targetReachedDistance;
}
// LEBENSLOGIK
//
public void TakeDamage(float damage)
{
if (IsDead || damage <= 0f)
return;
currentHealth -= damage;
Debug.Log(
gameObject.name +
" erhält " + damage +
" Schaden. HP: " +
currentHealth + "/" + maxHealth
);
if (currentHealth <= 0f)
{
Die();
}
}
private void Die()
{
if (IsDead)
return;
IsDead = true;
Debug.Log(
gameObject.name + " wurde besiegt!"
);
Destroy(gameObject);
}
// ANGRIFFSLOGIK (möglicherweise in anderem Skript)
//
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 544fe3d5f687b6b4bbe68d79ffb11bd8
+425
View File
@@ -0,0 +1,425 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemySpawner : MonoBehaviour
{
[System.Serializable]
public class EnemyTypeData
{
[Header("Enemy Prefabs")]
public List<GameObject> prefabVariants = new List<GameObject>();
[Header("Spawn Selection")]
[Min(0)]
public int spawnChance = 10;
public float danger = 0f;
[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 float time = 0f;
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.
time = time + Time.deltaTime;
//Debug.Log(time);
// Schwierigkeit erhöht sich.
difficultyRaise += raiseRate * Time.deltaTime;
// Budget wächst nur, wenn es nicht pausiert ist.
if (!pauseDifficultyGrowth)
{
difficulty += difficultyRaise * Time.deltaTime;
}
Debug.Log("Time: " + time + ", 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<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)
);
}
}
private bool TryBuildGroup(
out List<EnemyTypeData> group,
out float resultingDifficulty)
{
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 > time)
{
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,2 @@
fileFormatVersion: 2
guid: eed070947de809f4d9fd7c87e1d9b1c3
+193
View File
@@ -0,0 +1,193 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyTargetManager : MonoBehaviour
{
[Header("Target Points")]
[SerializeField]
private List<Transform> targetPoints = new List<Transform>();
[Header("Active Target Chance")]
[SerializeField]
[Range(0f, 1f)]
private float twoTargetsChance = 0.25f;
[Header("Switch Timing")]
[SerializeField]
private float minSwitchDelay = 15f;
[SerializeField]
private float maxSwitchDelay = 25f;
[Header("Target Selection")]
[SerializeField]
[Min(0f)]
private float equalDistanceTolerance = 0.1f; //In Unity-Einheiten!
// Wird ausgelöst, wenn sich die aktiven Ziele ändern.
public event Action OnTargetsChanged;
private void Start()
{
if (targetPoints.Count == 0)
{
Debug.LogError(
"EnemyTargetManager: Keine Target Points zugewiesen!",
this
);
return;
}
if (targetPoints.Count < 2)
{
Debug.LogWarning(
"EnemyTargetManager: Es sind weniger als 2 Target Points vorhanden. Zwei aktive Targets sind dadurch nicht möglich.",
this
);
}
if (minSwitchDelay > maxSwitchDelay)
{
Debug.LogWarning(
"EnemyTargetManager: minSwitchDelay ist größer als maxSwitchDelay. Werte werden getauscht.",
this
);
float temp = minSwitchDelay;
minSwitchDelay = maxSwitchDelay;
maxSwitchDelay = temp;
}
// Zu Beginn zufällig Targets aktivieren.
SetRandomActiveTargets();
// Danach regelmäßig wechseln.
StartCoroutine(TargetSwitchLoop());
}
private IEnumerator TargetSwitchLoop()
{
while (true)
{
float delay = UnityEngine.Random.Range(
minSwitchDelay,
maxSwitchDelay
);
yield return new WaitForSeconds(delay);
SetRandomActiveTargets();
}
}
private void SetRandomActiveTargets()
{
// Zuerst alle Targets deaktivieren.
foreach (Transform target in targetPoints)
{
if (target != null)
{
target.gameObject.SetActive(false);
}
}
// Entscheiden, ob 1 oder 2 Targets aktiv sein sollen.
int activeTargetCount = 1;
if (targetPoints.Count >= 2 &&
UnityEngine.Random.value < twoTargetsChance)
{
activeTargetCount = 2;
}
// Alle gültigen Targets sammeln.
List<Transform> availableTargets =
new List<Transform>();
foreach (Transform target in targetPoints)
{
if (target != null)
{
availableTargets.Add(target);
}
}
// Zufällige Targets aktivieren.
for (int i = 0; i < activeTargetCount; i++)
{
int randomIndex = UnityEngine.Random.Range(
0,
availableTargets.Count
);
Transform target =
availableTargets[randomIndex];
target.gameObject.SetActive(true);
// Verhindert doppelte Auswahl.
availableTargets.RemoveAt(randomIndex);
}
// Alle Gegner informieren.
OnTargetsChanged?.Invoke();
}
public Transform GetClosestActiveTarget(Vector3 position)
{
List<Transform> closestTargets =
new List<Transform>();
float closestDistance = float.MaxValue;
foreach (Transform target in targetPoints)
{
if (target == null ||
!target.gameObject.activeInHierarchy)
{
continue;
}
// Tatsächliche Entfernung in Unity-Einheiten.
float distance =
Vector3.Distance(
position,
target.position
);
// Eindeutig näher als die bisher gefundenen Targets.
if (distance <
closestDistance - equalDistanceTolerance)
{
closestDistance = distance;
closestTargets.Clear();
closestTargets.Add(target);
}
// Innerhalb der eingestellten Toleranz:
// Dieses Target gilt als gleich nah.
else if (Mathf.Abs(
distance - closestDistance
) <= equalDistanceTolerance)
{
closestTargets.Add(target);
}
}
if (closestTargets.Count == 0)
{
return null;
}
// Bei mehreren gleich nahen Targets zufällig wählen.
int randomIndex = UnityEngine.Random.Range(
0,
closestTargets.Count
);
return closestTargets[randomIndex];
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6ac3932d87de0d64180930ddb4e2f576
@@ -0,0 +1,6 @@
using UnityEngine;
public interface IMovementDirection
{
Vector2 MovementDirection { get; }
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 649a0945a4204564187826046e548805
+68
View File
@@ -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
+209
View File
@@ -0,0 +1,209 @@
using UnityEngine;
using DepthsOfGreed.HandbookUI;
public class Turret : MonoBehaviour
{
[Header("Tower Data")]
[SerializeField]
private HandbookTowerEntry towerData;
[Header("Turret Setup")]
[SerializeField]
private Transform turret;
[SerializeField]
private Transform tip;
[SerializeField]
private float rotationSpeed = 360f;
[Header("Projectile Setup")]
[SerializeField]
private GameObject bulletPrefab;
[SerializeField]
private float bulletSpeed = 8f;
private CombatDefinition combatStats;
private float nextAttackTime;
private Enemy currentTarget;
private void Start()
{
if (towerData == null)
{
Debug.LogError(
"TowerShooting: Kein HandbookTowerEntry zugewiesen!",
this
);
enabled = false;
return;
}
if (turret == null || tip == null || bulletPrefab == null)
{
Debug.LogError(
"TowerShooting: Turret, Tip oder Bullet Prefab fehlt!",
this
);
enabled = false;
return;
}
combatStats = towerData.CreateCombatSnapshot(0);
}
private void Update()
{
if (combatStats == null)
return;
// Ziel innerhalb der Reichweite suchen.
currentTarget = FindTarget();
if (currentTarget != null)
{
RotateTowardsTarget();
TryShoot();
}
else
{
// Zur ursprünglichen Ausrichtung zurückkehren.
RotateTowardsOrigin();
}
}
private Enemy FindTarget()
{
Enemy[] enemies =
FindObjectsByType<Enemy>(
FindObjectsSortMode.None
);
Enemy closestEnemy = null;
float closestDistance = float.MaxValue;
float rangeSquared =
combatStats.range * combatStats.range;
foreach (Enemy enemy in enemies)
{
if (enemy == null || enemy.IsDead)
continue;
Vector2 difference =
enemy.transform.position - transform.position;
float distanceSquared = difference.sqrMagnitude;
if (distanceSquared > rangeSquared)
continue;
if (distanceSquared < closestDistance)
{
closestDistance = distanceSquared;
closestEnemy = enemy;
}
}
return closestEnemy;
}
private void RotateTowardsTarget()
{
Vector3 worldDirection =
currentTarget.transform.position - turret.position;
// Richtung in den lokalen Raum der Turmbasis umwandeln.
Vector3 localDirection =
turret.parent != null
? turret.parent.InverseTransformDirection(worldDirection)
: worldDirection;
float targetAngle =
Mathf.Atan2(localDirection.y, localDirection.x)
* Mathf.Rad2Deg - 90f;
Quaternion targetRotation =
Quaternion.Euler(0f, 0f, targetAngle);
turret.localRotation = Quaternion.RotateTowards(
turret.localRotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
private void RotateTowardsOrigin()
{
turret.localRotation = Quaternion.RotateTowards(
turret.localRotation,
Quaternion.identity,
rotationSpeed * Time.deltaTime
);
}
private void TryShoot()
{
if (combatStats.attacksPerSecond <= 0f)
return;
if (Time.time < nextAttackTime)
return;
Shoot();
float attackInterval =
1f / combatStats.attacksPerSecond;
nextAttackTime = Time.time + attackInterval;
}
private void Shoot()
{
GameObject bulletObject = Instantiate(
bulletPrefab,
tip.position,
tip.rotation
);
Bullet bullet =
bulletObject.GetComponent<Bullet>();
if (bullet == null)
{
Debug.LogError(
"TowerShooting: Bullet Prefab besitzt kein TowerBullet-Skript!",
bulletObject
);
Destroy(bulletObject);
return;
}
bullet.Initialize(
tip.up,
bulletSpeed,
combatStats.damagePerHit,
combatStats.range
);
}
private void OnDrawGizmosSelected()
{
if (towerData == null)
return;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(
transform.position,
towerData.combat.range
);
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5ec586d6b5f1d324d89950df76c68b91