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

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d14ad0391252c4c44ae83d6fc8386cb3
guid: 89f34c5b2bc781e479a5da1cb918f3aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
+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
@@ -15,8 +15,20 @@ public class Enemy : MonoBehaviour, IMovementDirection
// 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>();
@@ -87,6 +99,10 @@ public class Enemy : MonoBehaviour, IMovementDirection
}
}
// BEWEGUNGSLOGIK
//
private void SelectClosestTarget()
{
if (targetManager == null)
@@ -133,4 +149,47 @@ public class Enemy : MonoBehaviour, IMovementDirection
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)
//
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
+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
-16
View File
@@ -1,16 +0,0 @@
using UnityEngine;
public class Gewehr : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
-2
View File
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 20a1554fc2589f941b773279b280cb4c